Mocking HttpClient GetAsync by using Moq library in Xunit test
Asked Answered
T

2

9

I am writing a simple unit test for this small service that simply calls external APIs:

public class ApiCaller : IApiCaller
{
    private readonly IHttpClientFactory _httpFactory;

    public ApiCaller(IHttpClientFactory httpFactory)
    {
        _httpFactory = httpFactory;
    }

    public async Task<T> GetResponseAsync<T>(Uri url)
    {
        using (HttpClient client = _httpFactory.CreateClient())
        {
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.Timeout = TimeSpan.FromSeconds(20);
            using (HttpResponseMessage response = await client.GetAsync(url))
            {
                response.EnsureSuccessStatusCode();
                string responseBody = await response.Content.ReadAsStringAsync();

                return JsonConvert.DeserializeObject<T>(responseBody);
            }

        }
    }
}

My first question is: it doesn't seem to be very common practice mocking and therefore testing such services and I am wondering if there is some specific explanation.

Second, I tried to write a simple unit test but I cannot Mock the GetAsync call since HttpClient doesn't implement any interface.

public class ApiCallerTest
{
    private readonly ApiCaller _target;
    private readonly Mock<IHttpClientFactory> _httpClientFactory;


    public ApiCallerTest()
    {
        _httpClientFactory = new Mock<IHttpClientFactory>();
        _target = new ApiCaller(_httpClientFactory.Object);
    }

    [Fact]
    public void WhenACorrectUrlIsProvided_ServiceShouldReturn()
    {


        var client = new HttpClient();
        _httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(client);

        var httpMessageHandler = new Mock<HttpMessageHandler>();

    }

}
Thies answered 9/5, 2019 at 16:42 Comment(1)
Another example here justsimplycode.com/2018/09/22/…Ashley
V
11

The code below is what you should use regardless of the method in the HttpClient class you use (GetAsync, PostAsync, etc.). All these methods are created for the convenience of the programmer. What they do is use the SendAsync method of the HttpMessageHandler class.

var mockHttpMessageHandler = new Mock<HttpMessageHandler>();

// Setup Protected method on HttpMessageHandler mock.
mockHttpMessageHandler.Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>()
    )
    .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
    {
        HttpResponseMessage response = new HttpResponseMessage();

        // configure your response here

        return response;
    });

And then you use it this way:

var httpClient = new HttpClient(mockHttpMessageHandler.Object);
var result = await httpClient.GetAsync(url, cancellationToken);

You can also take a look here How to create mock for httpclient getasync method?

Vas answered 12/3, 2020 at 21:20 Comment(1)
Thank you so much! The ability to read the request before determining what the response should be is key to making my tests work. That is, a login request should have a different response from a healthcheck request, for example.Sappanwood
B
3

Setup your Mock HttpMessageHandler first and pass it to the constructor of your HttpClient. Then you can setup a Mock for the GetAsync method on the handler like this:

        var httpMessageHandler = new Mock<HttpMessageHandler>();

        // Setup Protected method on HttpMessageHandler mock.
        httpMessageHandler.Protected()
            .Setup<Task<HttpResponseMessage>>(
                "GetAsync",
                ItExpr.IsAny<string>(),
                ItExpr.IsAny<CancellationToken>()
            )
            .ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
            {
                HttpResponseMessage response = new HttpResponseMessage();

                // Setup your response for testing here.

                return response;
            });

        var client = new HttpClient(httpMessageHandler.Object);

This is modified from a unit test I use to mockSendAsync, but it should be very similar.

Boredom answered 9/5, 2019 at 18:31 Comment(2)
Mmmm when setting up the HttpMessageHandler mock it throws an exception: "Member HttpMessageHandler.GetAsync does not exist"Thies
It should have been "SendAsync" instead of "GetAsync". Check my answer.Vas

© 2022 - 2024 — McMap. All rights reserved.