簡體   English   中英

使用 Moq 模擬 HttpClient

[英]Mock HttpClient using Moq

我想對一個使用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)));
}

我們如何解決這個問題?

該特定的重載方法不是虛擬的,因此無法被 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);
}

Moq 可以模擬受保護的方法,例如您可以在其構造函數中提供給 HttpClient 的 HttpMessageHandler 上的 SendAsync。

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/

使用 HttpClient 進行適當的模擬是一項艱巨的工作,因為它是在大多數人在 dotnet 中進行單元測試之前編寫的。 有時我會設置一個存根 HTTP 服務器,它根據與請求 url 匹配的模式返回預設響應,這意味着您測試真正的 HTTP 請求不是模擬,而是到 localhost 服務器。 使用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 的更多詳細信息和指南。

我最近不得不模擬 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));

有關更多信息,請在此處查看作者的博客文章。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM