2023. 1. 16. 00:00ㆍASPNET/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
![](https://blog.kakaocdn.net/dn/bOGmin/btrVo3OwBP2/A7c4bTvtGKjmHb9hKdTlf0/img.png)
자 이제 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();
}
}
관련영상
'ASPNET > ASPNET 7' 카테고리의 다른 글
인증서버 - JWT 서명 및 payload 암호화 (JWE,JWS) (4) | 2023.07.31 |
---|---|
Rest API Template 만들기 - EP 10 (Refactoring - Features) (0) | 2023.01.19 |
Rest API Template 만들기 - EP 08 (DB 를 통해 AccessToken 및 RefreshToken 관리) (0) | 2023.01.12 |
Rest API Template 만들기 - EP 07 (EF Core - Migration and Update) (0) | 2023.01.09 |
Rest API Template 만들기 - EP 06 (Authentication, Authorization with jwt) (0) | 2023.01.05 |