C에서 NUnit 및 Moq를 사용하여 HttpClient에 대한 셀 테스트 작성

HttpClient를 사용하여 GET 요청을 보내고 응답을 사용하여 추가 작업을 수행하는 클래스가 있습니다.이 클래스를 작성한 이상, 올바른 요청 헤더와 요청 방법을 사용하여 정확한 URI를 호출할 수 있도록 NUnit 을 계속 사용하길 원합니다.어떻게 했어요?
이 점을 더 잘 이해하기 위해 만든 프레젠테이션 프로젝트를 살펴보자.내 Github 페이지 here 에서 이 항목을 찾을 수 있습니다.EmployeeApiClientService라는 클래스를 만들었습니다. 이 클래스는 GetEmployeeAsync()라는 방법이 있습니다. 이 방법은employeeid를 입력 매개 변수로employee API에 GET 요청을 보내고uri에employeeid를 포함해서 Employee 대상을 되돌려줍니다.
public class EmployeeApiClientService
{
    private readonly HttpClient employeeHttpClient;

    public EmployeeApiClientService(HttpClient httpClient)
    {
        employeeHttpClient = httpClient;
    }

    //environment specific variables should always be set in a seperate config file or database. 
    //For the sake of this example I'm initialising them here.
    public static string testDatabase = "SloughDB";
    public static string environment = "TEST";

    public async Task<Employee> GetEmployeeAsync(int employeeId)
    {
        Employee employee = null;

        //Add headers
        employeeHttpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        employeeHttpClient.DefaultRequestHeaders.Add("tracking-id", Guid.NewGuid().ToString());

        //Conditional Headers
        if (environment == "TEST")
        {
            employeeHttpClient.DefaultRequestHeaders.Add("test-db", testDatabase);
        }

        HttpResponseMessage response = await employeeHttpClient.GetAsync($"http://dummy.restapiexample.com/api/v1/employee/{employeeId}");
        if (response.IsSuccessStatusCode)
        {
            employee = JsonConvert.DeserializeObject<Employee>(await response.Content.ReadAsStringAsync());
        }
        return employee;
    }
}
일단 네가 클래스를 다 썼을 때, 너는 단원 테스트를 쓰기 시작하지만, 너는 끊겼다.HttpClient 클래스에 사용할 인터페이스가 없습니다Moq. 단원 테스트 기간에 실제 단점을 만나고 싶지 않습니다.여기서 뭐하는 거야?
이 문제를 해결하는 방법에는 여러 가지가 있습니다.
방법 1: HttpClient 클래스에 대한 패키지 클래스 작성
이 방법은 HttpClientWrapper 같은 포장 클래스를 작성하고 포장 클래스에서 모든 HttpClient를 실현하는 방법을 작성한 다음에 실제 클래스에서 이 포장 클래스를 HttpClient가 아닌 의존항으로 사용해야 합니다.
그리고 단원 테스트에서 이 패키지 종류를 시뮬레이션하고 요청의 세부 사항을 검증할 수 있습니다.나에게 있어서 이런 방법은 작업량이 너무 많은 것 같을 뿐만 아니라, 간결한 실현도 아니다.
그래서 나는 주위를 둘러보니 HttpClient가 구조 함수를 다시 불러오고 HttpMessageHandler를 받아들이는 것을 발견했다.public HttpClient(HttpMessageHandler handler);이것이 바로 내가 두 번째 방법을 생각해 낸 이유다.
방법 2: HttpMessageHandler를 시뮬레이션하여 HttpClient 클래스에 전달
HttpMessageHandler는 보호된 방법SendAsync()이 있는데 이것은 모든 HttpClient의GET/POST/PATCH/PUT 비동기적인 방법으로 호출되는 하부 방법이다.우리가 해야 할 일은 이 종류를 모의하고, 테스트 용례에 따라 SendAsync를 설정해서 우리가 원하는 값을 받아들이고 되돌려 주는 것이다.우리는 최소한의 주문량을 사용하여 이 목적을 달성할 수 있다.
Moq는 에서 사용되는 아날로그 프레임입니다.NET에서 테스트할 셀을 베이스 종속 항목과 분리합니다.HttpMessageHandler에 대한 시뮬레이션을 만들고 이를 HttpClient의 재부팅 구조 함수에 전달할 수 있습니다.
이 점을 어떻게 실현하는지 봅시다.

아날로그 HttpMessageHandler
Moq를 사용하여 HttpMessageHandler의 아날로그 대상을 만들고 이를 HttpClient 클래스 구조 함수에 전달하며 테스트 설정에서 이 HttpClient 대상을 EmployeeApiClient 서비스 구조 함수에 전달합니다.
EmployeeApiClientService employeeApiClientService;
Mock<HttpMessageHandler> httpMessageHandlerMock;

[SetUp]
public void setUp()
{
    httpMessageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
    HttpClient httpClient = new HttpClient(httpMessageHandlerMock.Object);
    employeeApiClientService = new EmployeeApiClientService(httpClient);
}

SendAsync 메서드 설정
Moq에서는 HttpMessageHandler 클래스에서 보호되어 클래스 밖에서 접근할 수 없기 때문에 SendAsync() 방법을 직접 설정할 수 없습니다.
우리는 최소한의 주문량을 사용할 수 있다.보호된api는 시뮬레이션 대상에 대한 추가 방법을 제공합니다. 보호된 구성원의 이름을 사용하여 접근할 수 있습니다.
현재, 우리는 아날로그 HttpMessageHandler 대상의 .Protected() 방법을 설정하여, json 형식으로 Employee 대상이 있는 StatusCode 200을 되돌려줄 것입니다. 우리는 SendAsync() 방법을 사용하고, 단언 부분에서 이 방법에 대한 호출 횟수, 상세한 정보 요청 등을 검증할 수 있도록 ReturnsAsync() 방법을 사용할 것입니다.
httpMessageHandlerMock.Protected().Setup<Task<HttpResponseMessage>>(
    "SendAsync",
    ItExpr.IsAny<HttpRequestMessage>(),
    ItExpr.IsAny<CancellationToken>()
    ).ReturnsAsync(new HttpResponseMessage()
    {
       StatusCode = HttpStatusCode.OK,
       Content = new StringContent(JsonConvert.SerializeObject(new Employee()), Encoding.UTF8, "application/json")
    }).Verifiable();

SendAsync 호출 확인
단원 테스트의 단언 부분에서 우리는 검증Verify() 방법을 한 번만 호출할 것이다. 이것은 GET를 통해 호출을 요청한 것이다. 요청은 예상된 목표uri를 포함하고 요청은 모든 필수 헤더를 포함한다.
httpMessageHandlerMock.Protected().Verify(
    "SendAsync",
    Times.Exactly(1), 
    ItExpr.Is<HttpRequestMessage>(req =>
    req.Method == HttpMethod.Get  
    && req.RequestUri.ToString() == targetUri // veryfy the RequestUri is as expected
    && req.Headers.GetValues("Accept").FirstOrDefault() == "application/json" 
    && req.Headers.GetValues("tracking-id").FirstOrDefault() != null 
    && environment.Equals("TEST") ? 
                      req.Headers.GetValues("test-db").FirstOrDefault() == testDatabase : 
                      req.Headers.GetValues("test-db").FirstOrDefault() == null 
    ),
    ItExpr.IsAny<CancellationToken>()
    );

완전한 테스트 과정
테스트 용례를 포함하는 완전한 테스트 클래스는 요청을 정확하게 만들 수 있습니다. 아래와 같습니다.
[TestFixture]
class EmployeeApiClientServiceTests
{
    EmployeeApiClientService employeeApiClientService;
    Mock<HttpMessageHandler> httpMessageHandlerMock;

    //environment specific variables should always be set in a separate config file or database. 
    //For the sake of this example I'm initialising them here.
    string testDatabase = "SloughDB";
    string environment = "TEST";

    [SetUp]
    public void setUp()
    {
        httpMessageHandlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
        HttpClient httpClient = new HttpClient(httpMessageHandlerMock.Object);
        employeeApiClientService = new EmployeeApiClientService(httpClient);
    }


    [Test]
    public async Task GivenICallGetEmployeeAsyncWithValidEmployeeId_ThenTheEmployeeApiIsCalledWithCorrectRequestHeadersAsync()
    {
        //Arrange
        int employeeId = 1;
        string targetUri = $"http://dummy.restapiexample.com/api/v1/employee/{employeeId}";
        //Setup sendAsync method for HttpMessage Handler Mock
        httpMessageHandlerMock.Protected().Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.IsAny<HttpRequestMessage>(),
            ItExpr.IsAny<CancellationToken>()
            )
            .ReturnsAsync(new HttpResponseMessage()
            {
                StatusCode = HttpStatusCode.OK,
                Content = new StringContent(JsonConvert.SerializeObject(new Employee()), Encoding.UTF8, "application/json")
            })
            .Verifiable();

        //Act
        var employee = await employeeApiClientService.GetEmployeeAsync(employeeId);

        //Assert
        Assert.IsInstanceOf<Employee>(employee);

        httpMessageHandlerMock.Protected().Verify(
            "SendAsync",
            Times.Exactly(1), // verify number of times SendAsync is called
            ItExpr.Is<HttpRequestMessage>(req =>
            req.Method == HttpMethod.Get  // verify the HttpMethod for request is GET
            && req.RequestUri.ToString() == targetUri // veryfy the RequestUri is as expected
            && req.Headers.GetValues("Accept").FirstOrDefault() == "application/json" //Verify Accept header
            && req.Headers.GetValues("tracking-id").FirstOrDefault() != null //Verify tracking-id header is added
            && environment.Equals("TEST") ? req.Headers.GetValues("test-db").FirstOrDefault() == testDatabase : //Verify test-db header is added only for TEST environment
                                            req.Headers.GetValues("test-db").FirstOrDefault() == null
            ),
            ItExpr.IsAny<CancellationToken>()
            );
    }
}
따라서 HttpClient 호출을 테스트하기 위해 단원 테스트 용례를 쉽게 작성하고 요청의 각 방면을 검증하며 클래스가 응답을 처리하는 방법을 보았습니다.이 방법은 POST/PUT/PATCH 요청에도 사용할 수 있습니다. 왜냐하면 HttpClient의 모든 방법이 백엔드에서 HttpMessageHandler의 SendAsync() 방법을 사용하기 때문입니다.
나는 네가 이것이 매우 재미있다고 느끼기를 바란다.읽어주셔서 감사합니다!

좋은 웹페이지 즐겨찾기