Home > Mobile >  How to mock HttpClient.GetFromJsonAsync?
How to mock HttpClient.GetFromJsonAsync?

Time:08-10

I've got some code that calls HttpClient's GetFromJsonAsync however I'm struggling to mock the method call and was wondering how can I do this?

C# code:

public class Client : IClient
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly HttpClient _httpClient;

    public Client(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
        _httpClient = _httpClientFactory.CreateClient();
    }

    public async Task<List<ApiResponse>> GetData()
    {
        try
        {
            return await _httpClient.GetFromJsonAsync<ApiResponse>("endpointUrl"); // How to mock?
        }
        catch (Exception e)
        {
            throw;
        }

        return null;
    }
}

I've seen previous posts that suggest I should mock HttpMessageHandler but how do I mock the response back from the GetFromJsonAsync method call?

As per one of the suggested answers, I've done the following:

var httpClientMock = new Mock<HttpClient>();
httpClientMock.Setup(x => x.GetFromJsonAsync<ApiResponse>(It.IsAny<string>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new ApiResponse());

_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_httpClientFactoryMock.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(httpClientMock.Object);

However I receive the following error:

Message "Unsupported expression: x => x.GetFromJsonAsync<DataLookupResponse>(It.IsAny<string>(), It.IsAny<CancellationToken>())\nExtension methods (here: HttpClientJsonExtensions.GetFromJsonAsync) may not be used in setup / verification expressions."  

CodePudding user response:

If you create a mock of HttpClient you can then return this when calling _httpClientFactory.CreateClient();.

Something like this (haven't tested this code in my IDE so be aware of any typo's)

var httpClientMock = new Mock<HttpClient>();

httpClientMock.Setup(x => x.GetFromJsonAsync<ApiResponse>("endpointurl").Returns(...); httpClientFactoryMock.Setup(x => x.CreateClient()).Returns(httpClientMock.Object);

CodePudding user response:

Recently I've been unit testing my HttpClients and I had to solve the same problem as you.

Modify where needed. I'm using the IConfiguration to retrieve some application settings. The code to mock this has also been included in the code you can find below.

The call in the test is a mocked call. You don't need an internet connection for this call to succeed. You can specify any endpoint and call it with any configured response.

This means can return anything you want in your mocked call and use fake endpoint in order to not expose any sensitive data in your code.

Install the following NuGet Packages in your test project in order for my solution to work:

<PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />

My HttpClient:

public class MyHttpClient : IMyHttpClient
{
    private readonly IConfiguration _configuration;
    private readonly IHttpClientFactory _httpClientFactory;

    public MyHttpClient(IConfiguration configuration, IHttpClientFactory httpClientFactory)
    {
        _configuration = configuration;
        _httpClientFactory = httpClientFactory;
    }

    public async Task<SomeType> GetSomeInformationAsync()
    {
        var token = await FetchAccessToken();
        var client = CreateHttpClient(token);

        var endpoint = _configuration.GetValue<string>("Endpoints:SomeEndpoint");

        var response = client.GetAsync(endpoint);
        var content = await response.Result.Content.ReadAsStringAsync();

        return content;
    }

    private HttpClient CreateHttpClient(string accessToken)
    {
        var client = _httpClientFactory.CreateClient(nameof(MyHttpClient));
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        return client;
    }
}

My TestClass:

public class MyHttpClientTests
{
    private readonly MyHttpClient _sut;
    private readonly Mock<IConfiguration> _configurationMock = new();
    private readonly Mock<IHttpClientFactory> _httpClientFactoryMock = new();
    private readonly MockHttpMessageHandler _httpMessageHandlerMock = new();

    public MyHttpClientTests()
    {
        _sut = new MyHttpClient(_configurationMock.Object, _httpClientFactoryMock.Object);
    }

    [Fact]
    public async void GetSomeInformationTest_ShouldReturnSomething()
    {
        // Since you are mocking you don't need the real endpoint. 
        var endpoint = "/someNotExistingEndpoint/";

        var getSomeInformationValue = new Mock<IConfigurationSection>();
        getSomeInformationValue.Setup(x => x.Value).Returns(endpoint);

        // When I retrieve my configuration in my mocked HttpClient from 'Endpoints:SomeEndpoint' it will return the value '/someNotExistingEndpoint/'
        _configurationMock.Setup(x => x.GetSection(It.Is<string>(x => x == "Endpoints:SomeEndpoint"))).Returns(getSomeInformationValue.Object);

        // When the above endpoint is called I can respond with anything I want. In this case an StatusCode of OK and some JsonContent (application/json)).
        _httpMessageHandlerMock.When(endpoint).Respond(HttpStatusCode.OK, JsonContent.Create(new { Message = "thisIsSomeJsonResponse" }));

        _httpClientFactoryMock.Setup(x => x.CreateClient(nameof(MyHttpClient)))
            .Returns(new HttpClient(_httpMessageHandlerMock)
            {
                BaseAddress = new Uri("someBaseAdress")
            });

        var result = await _sut.GetSomeInformationAsync();

        // You can put your assertions here
    }
}
  • Related