在单元测试中无法模拟HttpClient PostAsync()

时间:2019-07-18 09:33:58

标签: .net .net-core moq xunit

我正在使用xUnit和Moq编写测试用例。

我正在尝试模拟HttpClient的PostAsync(),但抛出错误。

下面是用于模拟的代码。

   public TestADLS_Operations()
    {
        var mockClient = new Mock<HttpClient>();
        mockClient.Setup(repo => repo.PostAsync(It.IsAny<string>(), It.IsAny<HttpContent>())).Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));

        this._iADLS_Operations = new ADLS_Operations(mockClient.Object);
    }

错误:

  

不受支持的表达式:repo => repo.PostAsync(It.IsAny(),   It.IsAny())不可覆盖的成员(此处:   HttpClient.PostAsync)不能用于设置/验证   表达式。

屏幕截图:

enter image description here

5 个答案:

答案 0 :(得分:4)

不可覆盖的成员(此处为HttpClient.PostAsync)不能在设置/验证表达式中使用。

我也尝试以与您相同的方式来模拟HttpClient,并且得到相同的错误消息。


解决方案:

模拟HttpClient而不是模拟HttpMessageHandler

然后将mockHttpMessageHandler.Object赋予您的HttpClient,然后将其传递给您的产品代码类。之所以有效,是因为HttpClient在后​​台使用了HttpMessageHandler

// Arrange
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);
this._iADLS_Operations = new ADLS_Operations(client);

注意:您还需要一个

using Moq.Protected;

位于测试文件的顶部。

然后,您可以从测试中调用使用PostAsync的方法,而PostAsync将返回HTTP状态OK响应:

// Act
var returnedItem = this._iADLS_Operations.MethodThatUsesPostAsync(/*parameter(s) here*/);

优势: 模拟HttpMessageHandler意味着您不需要产品代码或测试代码中的额外类。


有用的资源:

  1. Unit Testing with the HttpClient
  2. How to mock HttpClient in your .NET / C# unit tests

答案 1 :(得分:3)

正如其他答案所解释的那样,您应该模拟HttpMessageHandler或HttpClientFactory,而不是HttpClient。这是一种常见的情况,有人为两个案例Moq.Contrib.HttpClient创建了一个帮助程序库。

General Usage示例的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));

对于HttpClientFactory:

var handler = new Mock<HttpMessageHandler>();
var factory = handler.CreateClientFactory();

// Named clients can be configured as well (overriding the default)
Mock.Get(factory).Setup(x => x.CreateClient("api"))
    .Returns(() =>
    {
        var client = handler.CreateClient();
        client.BaseAddress = ApiBaseUrl;
        return client;
    });

答案 2 :(得分:1)

使用IHttpClientFactory代替在代码中直接使用HttpClient实例。 然后,在测试中,您可以创建自己的IHttpClientFactory实现,该实现发送回连接到TestServer的HttpClient。

以下是您的假工厂外观的一个示例:

public class InMemoryHttpClientFactory: IHttpClientFactory
{
    private readonly TestServer _server;

    public InMemoryHttpClientFactory(TestServer server)
    {
        _server = server;
    }

    public HttpClient CreateClient(string name)
    {
        return _server.CreateClient();
    }
}

然后,您可以在测试中设置TestServer,并让您的自定义IHttpClientFactory为该服务器创建客户端:

public TestADLS_Operations()
{
    //setup TestServer
    IWebHostBuilder hostBuilder = new WebHostBuilder()
        .Configure(app => app.Run(
        async context =>
    {
        // set your response headers via the context.Response.Headers property
        // set your response content like this:
        byte[] content = Encoding.Unicode.GetBytes("myResponseContent");
        await context.Response.Body.WriteAsync(content);
    }));
    var testServer = new TestServer(hostBuilder)

    var factory = new InMemoryHttpClientFactory(testServer);
    _iADLS_Operations = new ADLS_Operations(factory);

    [...]
}

答案 3 :(得分:1)

您遇到的问题表明耦合程度很高,您可以通过引入中间吸收来解决它。您可能想创建一个聚合HttpClient并通过接口方法公开PostAsync的类:

// Now you mock this interface instead, which is a pretty simple task.
// I suggest also abstracting away from an HttpResponseMessage
// This would allow you to swap for any other transport in the future. All 
// of the response error handling could be done inside the message transport 
// class.  
public interface IMessageTransport
{
    Task SendMessageAsync(string message);
}

// In ADLS_Operations ctor:
public ADLS_Operations(IMessageTransport messageTransport)
{ 
    //...
}

public class HttpMessageTransport : IMessageTransport
{
    public HttpMessageTransport()
    {
        this.httpClient = //get the http client somewhere.
    }

    public Task SendMessageAsync(string message)
    {
        return this.httpClient.PostAsync(message);
    }
}

答案 4 :(得分:0)

Visit Blog

内置支持在HttpRequestMessage的HttpMethod和RequestUri属性上应用条件。这样,我们可以使用EndsWith方法为各种路径模拟HttpGet,HttpPost和其他动词,如下所述。

_httpMessageHandler.Protected()
      .Setup<Task<HttpResponseMessage>>("SendAsync", true,          
      *// Specify conditions for httpMethod and path
      ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Get
           && req.RequestUri.AbsolutePath.EndsWith($"{path}"))),*
      ItExpr.IsAny<CancellationToken>())
      .ReturnsAsync(new HttpResponseMessage
      {
           StatusCode = HttpStatusCode.OK,
           Content = new StringContent("_0Kvpzc")
       });