如何使用Moq基于URL对HttpClient(HttpMessageHandler)进行单元测试

4

给开发者们,我想要模拟 HttpMessageHandler 来测试一个 HttpClient,我的问题是如何基于 URL 和 Http 方法进行模拟?因此响应将是方法和 URL 的函数:

Get + "http://testdoc.com/run?test=true&t2=10   => return X
Get + "http://testdoc.com/walk?test=true&t2=10   => return Y
Post + "http://testdoc.com/walk   => return Z

这3个调用将返回不同的结果。

我的当前单元测试可以捕获所有情况:

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

谢谢。
2个回答

4

根据你的代码,我编写了以下代码,
可以模拟通过URL识别的两个不同请求。

var mockMessageHandler = new Mock<HttpMessageHandler>();

var content = new HttpContentMock(usersQueryResult);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

mockMessageHandler.Protected()
  .Setup<Task<HttpResponseMessage>>(
    "SendAsync",
    ItExpr.Is<HttpRequestMessage>(rm => 
      rm.RequestUri.AbsoluteUri.StartsWith("https://example.com/api/users?query=")),
    ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(
      new HttpResponseMessage
      {
        StatusCode = 200,
        Content = content
      };
    );

mockMessageHandler.Protected()
  .Setup<Task<HttpResponseMessage>>(
    "SendAsync",
    ItExpr.Is<HttpRequestMessage>(rm => 
      rm.RequestUri.AbsoluteUri.StartsWith("https://example.com/api/users/gurka/")),
    ItExpr.IsAny<CancellationToken>())
  .ReturnsAsync(
    new HttpResponseMessage
    {
      StatusCode = 201,
      Content = null, // In reality the user found but we don't care for this test.
    }
  );
}

private class HttpContentMock : HttpContent
{
  private readonly IList<AdUserDataContract> users;
  public HttpContentMock(IList<AdUserDataContract> users)
  {
    this.users = users;  }

  protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
  {
    var json = JsonSerializer.Serialize(users, typeof(AdUsersDataContract));
    var buffer = Encoding.ASCII.GetBytes(json);
    stream.Write(buffer, 0, buffer.Length);
    return Task.CompletedTask;
  }

  protected override bool TryComputeLength(out long length)
  {
    length = 1; // Well... not totally true is it?
    return true;
  }
}

这是一个非常好的解决方案!我用它来解决了一个问题,我使用SetupSequence()已经完成了90%,但需要Callback()方法来查询请求中需要在响应(SOAP)中的id值。经过两天的努力,现在它终于可以工作了。 - Jim

2
问题在于你告诉了moq设置要使用任何http请求消息:ItExpr.IsAny<HttpRequestMessage>(),因此对于HttpRequestMessage的任何实例,它都将始终返回相同的结果。
如果你需要不同的X结果,你需要创建X个不同的实例:
string firstUri = "http://testdoc.com/run?test=true&t2=10";
HttpRequestMessage httpRequestMessage_1 = new HttpRequestMessage
{
    RequestUri = new Uri(firstUri),
    Method = ...,
    Content = ...,
};

而不是使用ItExpr.IsAny<HttpRequestMessage>(),可以使用实例httpRequestMessage_1,如下所示:

.Setup<Task<HttpResponseMessage>>("SendAsync", httpRequestMessage_1, ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{  /* Something with X */ });

1
多个测试用例需要多个模拟,将尝试特定的请求消息并查看是否检查了值。你回答了它,谢谢。 - user1154422
很高兴能帮到你。 - Shahar Shokrani

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