How to moq HttpClientExtensions method "PostAsJsonAsync"?

Viewed 836

I need to write the unit test case around PostAsJsonAsync, which is extension method in HttpClientExtensions. I don't find any easy way to moq it.

Can someone help me in this.

3 Answers

Two ways I can think of:

  1. Use a framework like Moles: https://www.microsoft.com/en-us/research/project/moles-isolation-framework-for-net/ This way you can replace extensions or any other methods with your own, returning the value you want

  2. This is my preferred way. In such cases, wrap the service inside a proxy Interface. You might find it called adapter pattern elsewhere but in my opinion you are just abstracting the actions and proxying the data.

So create the IHttpClientProxy and the corresponding concrete implementation that will use any extention you like. Pass the IHttpClientProxy to your class and mock it as you like.

As mentioned in the comments, there is no way the mocking frameworks to mock static methods this way. Frameworks like Moq, only mock virtual or abstract methods (interface methods are inherently abstract) to guide towards better design.

The issue for me was that I was not understanding that PostAsJsonAsync was actually a convenience method that ultimately calls SendAsync on the handler. So many of the answers you find relating to mocking HTTP client are accurate. The gist is that you are indeed mocking the HttpMessageHandler, and using a concrete HttpClient. The missing piece was that you still need to perform setup and verification on 'SendAsync', not PostAsJsonAsync. Here is the code that worked for me to test PostAsJsonAsync, because SendAsync gets called on the handler:

// Arrange

var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
    .Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>()
    )
    .ReturnsAsync(new HttpResponseMessage()
    {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent("[{'id':1,'value':'1'}]"),
    })
    .Verifiable();

// new up a real HttpClient, passing in the mocked handler
var httpClient = new HttpClient(handlerMock.Object)
{
    BaseAddress = new Uri("http://example.com")
};

// replace with an instance of your actual concrete client
var yourClient = new YourClientHere(httpClient);

// Act
// perform the action on yourClient that makes the PostAsJsonAsync call

// Assert
handlerMock.Protected().Verify(
    "SendAsync",
    Times.Exactly(accessRights.Count),
    ItExpr.IsAny<HttpRequestMessage>(),
    ItExpr.IsAny<CancellationToken>()
);

This article helped me get everything set up correctly: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/

I am using NSubstitute for mocking this extension method on HttpClient and it seems seems to work fine without the use of Moles or an adapter.

public class ApiClientTests
{

    private HttpClient _client;
    private string _url;
    private ModelDto _testModel;

    public void ApiClientTests()
    {
        _client = Substitute.For<HttpClient>();
        _client.BaseAddress = new Uri("http://fakeUrl/api/")

        _url = "Models/";
        _testModel = new ModelDto
        {
            Id = 1,
            Name = "Model Name",
            Description = "Model Description",
            Outputs = new Dictionary<string, ModelOutputDto>(),
            Parameters = new Dictionary<string, ModelParamDto>(),
            Active = true
        };

    }

    [Fact]
    public async Task CreateItemAsync_ValidResponseCode_ReturnsNewResourceUri()
    {
        // Arrange

        var returnUri = $"{_client.BaseAddress}{_url}";

        var returnThis = new HttpResponseMessage(System.Net.HttpStatusCode.Created);
        returnThis.Headers.Location = new Uri(returnUri);

        _client.PostAsJsonAsync(_url, _testModel).ReturnsForAnyArgs(Task.FromResult(returnThis));

        var apiClient = new ApiClient<ModelDto>(_client);

        // Act
        var result = await apiClient.CreateItemAsync(_testModel, _url);

        // Assert
        await _client.ReceivedWithAnyArgs().PostAsJsonAsync(_url, _testModel);
        result.AbsoluteUri.Should().BeEquivalentTo(returnUri);
    }
Related