使用Moq模拟HttpClient

17

我想对一个使用HttpClient的类进行单元测试。我们将HttpClient对象注入了类构造函数中。

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

现在我们希望对ClassA.SendRequest方法进行单元测试。我们使用Ms Test作为单元测试框架,使用Moq进行模拟。

当我们尝试模拟HttpClient时,会抛出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)));
}

我们如何解决这个问题?

4个回答

26

特定的重载方法不是虚拟的,因此无法被Moq覆盖。

public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);

这就是为什么它会抛出NotSupportedException的原因。

你要查找的虚方法是这个方法。

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

然而,嘲弄HttpClient不像看起来那么简单,因为它有其内部消息处理程序。

我建议使用一个具体的客户端和自定义的消息处理程序存根,这将允许在伪造请求时更加灵活。

这里是一个委托处理程序存根的示例。

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

请注意,默认构造函数基本上是在模拟之前您尝试模拟的内容。它还允许使用请求的委托进行更多自定义场景。

使用存根,测试可以重构为类似以下内容

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

12

使用Moq可以模拟保护方法,例如HttpMessageHandler上的SendAsync方法,您可以在构造函数中提供给HttpClient。

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

源自 https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/


10

我最近需要模拟HttpClient,使用了Moq.Contrib.HttpClient。它正好符合我的需求,使用简单,因此我想分享出来。

以下是一般用法示例:

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

更多信息请查看作者的博客文章这里


太棒了!在多请求-响应场景下,有助于我 - .NET 6.0 - Soren

8

使用HttpClient进行适当的模拟是一项艰巨的工作,因为它是在大多数人在dotnet中进行单元测试之前编写的。有时我会设置一个存根HTTP服务器,该服务器基于匹配请求URL的模式返回预设响应,这意味着您可以测试真实的HTTP请求而不是模拟请求,但要将其发送到本地主机。使用WireMock.net使这变得非常容易,并且运行速度足以满足我的大部分单元测试需求。

因此,不要使用http://some-domain.in,而是在某个端口上设置本地主机服务器,然后:

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

您可以在此处找到有关在测试中使用wiremock的更多详细信息和指南


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接