Mock HttpClient using Moq

22,576

Solution 1

That particular overload method is not virtual so is unable to be overridden by Moq.

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);

Which is why it throws NotSupportedException

The virtual method you are looking for is this method

public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);

However mocking HttpClient is not as simple as it seems with its internal message handler.

I suggest using a concrete client with a custom message handler stub that will allow for more flexibility when faking the request.

Here is an example of a delegating handler stub.

public class DelegatingHandlerStub : DelegatingHandler {
    private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
    public DelegatingHandlerStub() {
        _handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
    }

    public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
        _handlerFunc = handlerFunc;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        return _handlerFunc(request, cancellationToken);
    }
}

Note the default constructor is doing basically what you were trying to mock before. It also allows for more custom scenarios with a delegate for the request.

With the stub, the test can be refactored to something like

public async Task _SendRequestAsync_Test() {
    //Arrange           
    var handlerStub = new DelegatingHandlerStub();
    var client = new HttpClient(handlerStub);
    var sut = new ClassA(client);
    var obj = new SomeObject() {
        //Populate
    };

    //Act
    var response = await sut.SendRequest(obj);

    //Assert
    Assert.IsNotNull(response);
    Assert.IsTrue(response.IsSuccessStatusCode);
}

Solution 2

Moq can mock out protected methods, such as SendAsync on the HttpMessageHandler that you can provide to HttpClient in its constructor.

var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
    .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(new HttpResponseMessage
    {
        StatusCode = HttpStatusCode.OK
     });

var client = new HttpClient(mockHttpMessageHandler.Object);

Copied from https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/

Solution 3

Propper mocking with HttpClient is hard work as it was written before most people did unit testing in dotnet. Sometimes I setup a stub HTTP server that returns canned responses based on pattern matching the request url, meaning you test real HTTP requests not mocks but to a localhost server. Using WireMock.net makes this really easy and runs fast enough to satisfy most of my unit testing needs.

So instead of http://some-domain.in use a localhost server setup on some port, and then:

var server = FluentMockServer.Start(/*server and port can be setup here*/);
server.Given(
      Request.Create()
      .WithPath("/").UsingPost()
   )
   .RespondWith(
       Response.Create()
       .WithStatusCode(200)
       .WithHeader("Content-Type", "application/json")
       .WithBody("{'attr':'value'}")
   );

You can find a more details and guidance on using wiremock in tests here.

Solution 4

I recently had to mock HttpClient, and I used Moq.Contrib.HttpClient. It was what I needed, and simple to use, so I thought I'd throw it out there.

Here is an example of general usage:

// All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();

// A simple example that returns 404 for any request
handler.SetupAnyRequest()
    .ReturnsResponse(HttpStatusCode.NotFound);

// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
    .ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");

// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
    .ReturnsResponse(bytes, configure: response =>
    {
        response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
    })
    .Verifiable(); // Naturally we can use Moq methods as well

// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));

For more info, check out author's blog post here.

Share:
22,576
Amitava Karan
Author by

Amitava Karan

Updated on April 07, 2021

Comments

  • Amitava Karan
    Amitava Karan about 3 years

    I would like to unit test a class that uses HttpClient. We injected the HttpClient object in the class constructor.

    public class ClassA : IClassA
    {
        private readonly HttpClient _httpClient;
    
        public ClassA(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }
    
        public async Task<HttpResponseMessage> SendRequest(SomeObject someObject)
        {
            //Do some stuff
    
            var request = new HttpRequestMessage(HttpMethod.Post, "http://some-domain.in");
    
            //Build the request
    
            var response = await _httpClient.SendAsync(request);
    
            return response;
        }
    }
    

    Now we would like to unit test the ClassA.SendRequest method. We are using Ms Test for unit testing framework and Moq for mocking.

    When we tried to mock the HttpClient, it throws NotSupportedException.

    [TestMethod]
    public async Task SendRequestAsync_Test()
    {
        var mockHttpClient = new Mock<HttpClient>();
    
        mockHttpClient.Setup(
            m => m.SendAsync(It.IsAny<HttpRequestMessage>()))
        .Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
    }
    

    How can we solve this issue?