Mock HttpClient using Moq

佐手、 提交于 2019-12-01 04:11:10

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);
}

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!