Problems, solutions and recommendations - NextensArelB/SwaggerGenerationTool GitHub Wiki

On this page we list problems and the possible solutions when creating c# Unittets tests.

[[TOC]]

Difficult to mock HTTPClient

##Problem when you are creating a Unittest for a method that involkes a API call you might encounter that mocking the HTTPClient is not a easy task for various reasons:

  • You need to create the HTTPCLient object using the HTTPClientFactory you might be tempted to do this:
[TestClass]
public class MyApiTestClass
{
private Mock<IHttpClientFactory> _httpClientFactoryMock = null!;
private HttpClient _httpClient = null!;

[TestInitialize]
public void Startup()
{
_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_httpMessageHandler = new Mock<HttpMessageHandler>();
_httpClient = new HttpClient(_httpMessageHandler.Object);
_httpClientFactoryMock.Setup(_ => _.CreateClient(It.IsAny<string>()))
   .Returns(_httpClient).Verifiable();
}
}
  • If you perform multiple API calls in the same function you need to set up the HttpMessageHandler to return for each call a specific HttpResponseMessage with a mocked reponse, this clutters your code. You might be tempted to do this:
_httpMessageHandler.Protected()
   .SetupSequence<Task<HttpResponseMessage>>("SendAsync",
      ItExpr.IsAny<HttpRequestMessage>(),
      ItExpr.IsAny<CancellationToken>())
   .ReturnsAsync(new HttpResponseMessage() { Content = new StringContent(mockResponseRgsSchemas), StatusCode = HttpStatusCode.OK })
   .ReturnsAsync(new HttpResponseMessage() { Content = new StringContent(mockResponseRgsBrugstaat), StatusCode = HttpStatusCode.OK })
   .ReturnsAsync(new HttpResponseMessage() { Content = new StringContent(mockDivisionResponse), StatusCode = HttpStatusCode.OK })
   .ReturnsAsync(new HttpResponseMessage() { Content = new StringContent(mockDivisionDetailsResponse), StatusCode = HttpStatusCode.OK });

Where the mock reponses are the actual reponse from the API call.

  • You need to mock a number of dependent objects

##Analyse Altough the sample above works there is a more elegant and maintainable way of mocking the HttpClient.

##Solution

[TestClass]
public class MyApiTestClass
{

private HttpClient _httpClient;

[TestInitialize]
public async Task Startup()
{
_httpClient = new HttpClient() { BaseAddress = _baseAddress };
_mockHttpFactory = new Mock<IHttpClientFactory>();
_mockHttpFactory.Setup(_ => _.CreateClient(HttpClientNameConstants.SomeHttpClient))
         .Returns(_httpClient).Verifiable();
}

    [TestMethod]
    public async Task GetSomethingFromApi_ReturnsData_ItShouldReturnPopulatedObject()
    {
      [...]
        _httpClient = new HttpClient(new HttpClient_WithResponse_DelegateHandler<string>(HttpStatusCode.OK, 
            SomeMockResponses.SomeData))
            { BaseAddress = _baseAddress };
        _mockHttpFactory.Setup(_ => _.CreateClient(HttpClientNameConstants.VismaHttpClient))
                .Returns(_httpClient);
      [...]
    }
}

This is all possible because we have ceated some Httpclient Delegate classes:

Delegate Httpclient classes

public class HttpClient_WithResponse_DelegateHandler<T> : DelegatingHandler
{
    HttpStatusCode _statusCode;
    private T _returnObject;

    public HttpClient_WithResponse_DelegateHandler(HttpStatusCode statusCode, T returnObject)
    {
        _statusCode = statusCode;
        _returnObject = returnObject;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpContent content;

        if (typeof(T) == typeof(String))
        {
            content = new StringContent(_returnObject as string);
        }
        else
        {
            content = JsonContent.Create(_returnObject);
        }

        HttpResponseMessage response = new()
        {
            StatusCode = _statusCode,
            Content = content
        };

        return Task.FromResult(response);
    }
}

public class HttpClient_WithResponses_DelegateHandler<T> : DelegatingHandler
{
    private Dictionary<string, HttpMockResponse<T>> _returnObject;

    public HttpClient_WithResponses_DelegateHandler(Dictionary<string, HttpMockResponse<T>> returnObject)
    {
        _returnObject = returnObject;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpContent content;
        HttpStatusCode statusCode;
        string key = Uri.UnescapeDataString(request.RequestUri.PathAndQuery);

        if (typeof(T) == typeof(String))
        {
            HttpMockResponse<T> data = _returnObject.Where(x => x.Key == key).Select(x => x.Value).FirstOrDefault();
            if (data == null) { throw new ArgumentNullException($"Missing http response for request {key}"); }
            content = new StringContent(data.Object as string);
            statusCode = data.HttpStatusCode;
        }
        else
        {
            HttpMockResponse<T> data = _returnObject.Where(x => x.Key == key).Select(x => x.Value).FirstOrDefault();
            if (data == null) { throw new ArgumentNullException($"Missing http response for request {key}"); }
            content = JsonContent.Create(data.Object);
            statusCode = data.HttpStatusCode;
        }

        HttpResponseMessage response = new()
        {
            StatusCode = statusCode,
            Content = content
        };

        return Task.FromResult(response);
    }
}

public class HttpMockResponse<ResponseObject>
{
    public HttpMockResponse(ResponseObject obj, HttpStatusCode code)
    {
        Object = obj;
        HttpStatusCode = code;
    }

    public ResponseObject Object { get; private set; }
    public HttpStatusCode HttpStatusCode { get; private set; }
}

How to handle multiple API calls in one test function

When your method that you are testing makes multiple API calls to collect data, you can do that in one test function by creating a Dictionary having request Url's and mock reponses and pass that into the HttpClient deligate in one go, for example:

[...]
        Dictionary<string, HttpMockResponse<string>> responses = new()
        {
            { "/v2/fiscalyears", new HttpMockResponse<string>(VismaMockResponses.Periods, HttpStatusCode.OK) },
            { "/v2/accounts/3d2bac5a-c643-4075-8ae0-943dc1ee65c4?$page=1&$pagesize=1000", new HttpMockResponse<string>(VismaMockResponses.Accounts, HttpStatusCode.OK) },
            { "/v2/accounts/fb581370-5b88-4199-848e-d268e30fd551?$page=1&$pagesize=1000", new HttpMockResponse<string>(VismaMockResponses.Accounts, HttpStatusCode.OK) },
            { "/v2/accountbalances/2018-12-31?$page=1&$pagesize=%message1000", new HttpMockResponse<string>(VismaMockResponses.AccountBalances, HttpStatusCode.OK) },
            { "/v2/accountbalances/2019-12-31?$page=1&$pagesize=%message1000", new HttpMockResponse<string>(VismaMockResponses.AccountBalances, HttpStatusCode.OK) },
            { "/v2/fiscalyears/openingbalances?$page=1&$pagesize=1000", new HttpMockResponse<string>(VismaMockResponses.AccountBalances, HttpStatusCode.OK) },
            { "/v2/companysettings", new HttpMockResponse<string>(VismaMockResponses.CompanySettings, HttpStatusCode.OK) }
        };

        _httpClient = new HttpClient(new HttpClient_WithResponses_DelegateHandler<string>(responses))
            { BaseAddress = _baseAddress };
        _mockHttpFactory.Setup(_ => _.CreateClient(HttpClientNameConstants.VismaHttpClient))
                .Returns(_httpClient);
        var handler = new VismaFinancial(_loggerMock.Object, _mockHttpFactory.Object, _mockAccessTokenHandler.Object);

// Assert
[...]
⚠️ **GitHub.com Fallback** ⚠️