Rest API Template 만들기 - EP 09 (UnitTest)

2023. 1. 16. 00:00ASPNET/ASPNET 7

반응형

지금 까지 인증에 대한 처리를 해보았다.

OAuth 를 처리 하거나 OpenIdConnect 를 한것은 아니었다. 

하지만 jwt token 을 이용하여 특정 API 를 접근하기 위해 필요한 인증처리에 대부분을 해 보았다. 

이제 구현에서 약간 벗어나서 code 의 품질에 대해 신경써야 할 때이다. 

 

그런데 code 품질을 높이려면 우선 신경써야 할 것이 있다. 

Refactoring 이라는 과정을 통해 단계 단계 기존 code 를 변경할 텐데

이과정에서 정상 동작하던 어떤 method 가 refactoring 이후 비정상적으로 동작할 수있다.

이러한 문제를 해결하기 위해 우리는 Unit Test 를 통해 test 를 자동화 한다. 

**Unit Test 이지만 flow test 또는 integration test 에 더 가깝다.**

 

이제 새 프로젝트를 추가하자 (xUnit Test Project 를 고르자)

Project Name 을 UnitTest 라고 설정하고 Next 하자 

Framework 은 최신 framework 을 기준으로 하자.

기본으로 생성된 UniTest1.cs 를 삭제하자. (Usings.cs 는 삭제하면 안된다. global using 용이다.)

 

개발자 명령 프롬프트로 이동

Ctrl + ` 또는 View --> Terminal click --> 개발자 명령 프롬프트로 이동

개발자 명령 프롬프트에서 UniTest 프로젝트 폴더로 이동

**이미 UnitTest 프로젝트 폴더에 있다면 이동하지 않아도 된다. **

(현재 [솔루션명].sln 파일이 있는 폴더라면 [프로젝트명].csproj 파일이 있는 폴더로 이동)

dotnet add package Microsoft.AspNetCore.Mvc.Testing
이제  UnitTest 에 Add Project Reference 를 하고 ApiServer 를 선택하자

 

자 이제 BaseTest 를 하기전에 몇가지 class 를 ApiServer Project 에 추가 하겠다. 

 

Shared/Extensions/JsonExtensions

using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

namespace ApiServer.Shared.Extensions;

public static class JsonExtensions
{
    public static string SerializeToJson(this object source, bool writeIndented = true, bool propertyNameCaseInsensitive = true, JavaScriptEncoder? encoder = null, bool includeFields = false)
    {

        string retValue = string.Empty;
        if (encoder == null)
            encoder = JavaScriptEncoder.Create(UnicodeRanges.All);

        var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = propertyNameCaseInsensitive, WriteIndented = writeIndented, Encoder = encoder, IncludeFields = includeFields };
        retValue = JsonSerializer.Serialize(source, options);

        return retValue;
    }


    public static T? DeserializeFromJson<T>(this string value, bool writeIndented = true, bool propertyNameCaseInsensitive = true, JavaScriptEncoder? encoder = null, bool includeFields = false)
    {
        if (encoder == null)
            encoder = JavaScriptEncoder.Create(UnicodeRanges.All);

        var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = propertyNameCaseInsensitive, WriteIndented = writeIndented, Encoder = encoder, IncludeFields = includeFields };
        var retValue = JsonSerializer.Deserialize<T>(value, options);
        return retValue;
    }

    public static dynamic? DeserializeFromJson(this string value, bool writeIndented = true, bool propertyNameCaseInsensitive = true, JavaScriptEncoder? encoder = null, bool includeFields = false)
    {
        if (encoder == null)
            encoder = JavaScriptEncoder.Create(UnicodeRanges.All);

        var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = propertyNameCaseInsensitive, WriteIndented = writeIndented, Encoder = encoder, IncludeFields = includeFields };
        var retValue = JsonSerializer.Deserialize<dynamic>(value, options);
        return retValue;
    }

    public static async Task<T?> ReadAsAsync<T>(this HttpContent content, bool writeIndented = false, bool propertyNameCaseInsensitive = true, bool includeFields = false)
    {
        var options = new System.Text.Json.JsonSerializerOptions
        { WriteIndented = writeIndented, PropertyNameCaseInsensitive = propertyNameCaseInsensitive, IncludeFields = includeFields };
        return await JsonSerializer.DeserializeAsync<T>(await content.ReadAsStreamAsync(), options);
    }
}

 

BaseTest.cs

Test 들에서 공통적으로 쓰이는 method 들을 여기에 넣는다. (CommonRegister, CommonLogin)

WebApplicationFactory 를 초기화 하고 HttpClient 도 초기화 한다. 

~Base 소멸자에서 WebApplicationFactory 와 HttpClient 를 dispose 해준다. 

using ApiServer.Features.User;
using ApiServer.Helper;
using ApiServer.Shared.Extensions;
using Infrastructure;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestPlatform.TestHost;
using System.Text;

namespace UnitTest;
public abstract class BaseTest
{
    protected readonly ITestOutputHelper _testOutput = default!;

    protected WebApplicationFactory<Program> _application = default!;
    protected HttpClient _client = default!;
    protected readonly ApiServerContext _context = default!;
    public BaseTest(ITestOutputHelper testOutput)
    {
        _testOutput = testOutput;
        InitApplication();

        var scope = _application.Server.Services.GetService<IServiceScopeFactory>().CreateScope();
        _context = scope.ServiceProvider.GetRequiredService<ApiServerContext>();
    }
    private void InitApplication()
    {
        _application = new WebApplicationFactory<Program>();
        _client = _application.CreateClient();
    }
    ~BaseTest() 
    {
        _context.Dispose();
        _client.Dispose();
        _application.Dispose();
    }

    protected async Task<Register.Response?> CommonRegister(string id = "Test", string password = "1234")
    {
        var request = new Register.Command
        {
            Id = id,
            Name = id,
            Password = password,
        };

        var data = new StringContent(content: request.SerializeToJson(), encoding: Encoding.UTF8, mediaType: "application/json");
        var responseMessage = await _client.PostAsync("/user/register", data);
        var response = await responseMessage.Content.ReadAsAsync<Register.Response>();
        return response;
    }

    protected async Task<Login.Response?> CommonLogin(string id = "Test", string password = "1234")
    {
        var request = new Login.Command
        {
            Id = id,
            Password = EncryptionHashHelper.EncryptPassword(password),
        };

        var data = new StringContent(content: request.SerializeToJson(), encoding: Encoding.UTF8, mediaType: "application/json");
        var responseMessage = await _client.PostAsync("/user/login", data);
        var response = await responseMessage.Content.ReadAsAsync<Login.Response>();
        return response;
    }
}

 

UserTest.cs

_client 이용해서 실제 api 를 호출 한다. 

아래 코드를 통해서 Bearer token 을 header 에 설정 하낟. 

...
_client.DefaultRequestHeaders.Authorization 
= new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", preResponse?.AccessToken);
...

아래는 전체 코드이다.

using ApiServer.Features.User;
using ApiServer.Helper;
using ApiServer.Shared.Extensions;
using Microsoft.EntityFrameworkCore;
using System.Text;

namespace UnitTest;

[Collection("Sequential")]
public class UserTest : BaseTest
{
    public UserTest(ITestOutputHelper testOutput) : base(testOutput) { }

    [Fact]
    public async Task Register()
    {
        await _context.Users.ExecuteDeleteAsync();

        var request = new Register.Command
        {
            Id = "Test",
            Name = "Test",
            Password = "1234",
        };

        var data = new StringContent(content: request.SerializeToJson(), encoding: Encoding.UTF8, mediaType: "application/json");
        var responseMessage = await _client.PostAsync("/user/register", data);
        _testOutput.WriteLine(responseMessage.ToString());
        Assert.True(responseMessage.IsSuccessStatusCode);
        var response = await responseMessage.Content.ReadAsAsync<Register.Response>();
        _testOutput.WriteLine(response?.SerializeToJson());
        Assert.True(response?.Result);

        await _context.Users.ExecuteDeleteAsync();
    }

    [Fact]
    public async Task Login()
    {
        await _context.Logins.ExecuteDeleteAsync();
        await _context.Users.ExecuteDeleteAsync();

        await CommonRegister();
        var request = new Login.Command
        {
            Id = "Test",
            Password = EncryptionHashHelper.EncryptPassword("1234"),
        };

        var data = new StringContent(content: request.SerializeToJson(), encoding: Encoding.UTF8, mediaType: "application/json");
        var responseMessage = await _client.PostAsync("/user/login", data);
        _testOutput.WriteLine(responseMessage.ToString());
        Assert.True(responseMessage.IsSuccessStatusCode);
        var response = await responseMessage.Content.ReadAsAsync<Login.Response>();
        _testOutput.WriteLine(response?.SerializeToJson());
        Assert.True(response?.Result);

        await _context.Logins.ExecuteDeleteAsync();
        await _context.Users.ExecuteDeleteAsync();
    }


    [Fact]
    public async Task Logout()
    {
        await _context.Logins.ExecuteDeleteAsync();
        await _context.Users.ExecuteDeleteAsync();

        await CommonRegister();
        var preResponse = await CommonLogin();

        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", preResponse?.AccessToken);
        var responseMessage = await _client.PostAsync("/user/logout", null);
        _testOutput.WriteLine(responseMessage.ToString());
        Assert.True(responseMessage.IsSuccessStatusCode);
        var response = await responseMessage.Content.ReadAsAsync<Logout.Response>();
        _testOutput.WriteLine(response?.SerializeToJson());
        Assert.True(response?.Result);

        _client.DefaultRequestHeaders.Remove("Bearer");
        await _context.Logins.ExecuteDeleteAsync();
        await _context.Users.ExecuteDeleteAsync();
    }

    [Fact]
    public async Task Refresh()
    {
        await _context.Logins.ExecuteDeleteAsync();
        await _context.Users.ExecuteDeleteAsync();

        await CommonRegister();
        var preResponse = await CommonLogin();

        _client.DefaultRequestHeaders.Authorization =
            new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", preResponse?.AccessToken);
        var responseMessage = await _client.PostAsync($"/user/refresh?refreshToken={preResponse?.RefreshToken}", null);
        _testOutput.WriteLine(responseMessage.ToString());
        Assert.True(responseMessage.IsSuccessStatusCode);
        var response = await responseMessage.Content.ReadAsAsync<Refresh.Response>();
        _testOutput.WriteLine(response?.SerializeToJson());
        Assert.True(response?.Result);

        _client.DefaultRequestHeaders.Remove("Bearer");
        await _context.Logins.ExecuteDeleteAsync();
        await _context.Users.ExecuteDeleteAsync();
    }
}

 

 

 

관련영상

https://youtu.be/M1hRUOOUEOs

 

 

반응형