具有多个请求的模拟 HttpClient

Per*_*rcy 4 c# moq httpclient

我正在尝试对调用 API 的函数进行单元测试。我已经使用如下模拟成功完成了此操作HttpMessageHandler,它允许我伪造来自 API 的响应:

private static Mock<HttpMessageHandler> GetMockHttpMessageHandler(string mockResponse)
{
    var mockMessageHandler = new Mock<HttpMessageHandler>();
    mockMessageHandler.Protected()
        .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
        .Returns(Task.FromResult(new HttpResponseMessage
        {
            StatusCode = HttpStatusCode.OK,
            Content = new StringContent(mockResponse)
        }));
    return mockMessageHandler;
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。我已经能够测试一半的函数,后半部分进行另一个 api 调用 - 然后两个响应都被包装到系统使用的对象中。问题是,第二个 api 需要有不同的模拟响应。

我以为我可以将ItExpr.IsAny<HttpRequestMessage>()上面的代码更改为new HttpRequestMessage(HttpMethod.Post, "http://LiveUrl.com/AuthenticateUserCredential"),然后Setup/Returns根据 URI 更改响应,但我尝试如下(只有一个Setup/Return要测试,我没有破坏前半部分测试)

private static Mock<HttpMessageHandler> GetMockHttpMessageHandler(string mockResponse)
{
    var mockMessageHandler = new Mock<HttpMessageHandler>();
    mockMessageHandler.Protected()
        .Setup<Task<HttpResponseMessage>>("SendAsync", new HttpRequestMessage(HttpMethod.Post, "http://LiveUrl.com/AuthenticateUserCredential"), ItExpr.IsAny<CancellationToken>())
        .Returns(Task.FromResult(new HttpResponseMessage
        {
            StatusCode = HttpStatusCode.OK,
            Content = new StringContent(mockResponse)
        }));

    return mockMessageHandler;
}
Run Code Online (Sandbox Code Playgroud)

现在上面的内容破坏了第一个 api 调用 - 我得到以下响应:

处理程序未返回响应消息

现在我陷入了困境——我想做的事情可能吗?

小智 11

认为在 Moq 中使用 SetupSequence 可以解决这个问题:

private HttpClient GetHttpClientWithHttpMessageHandlerSequenceResponseMock(List<Tuple<HttpStatusCode,HttpContent>> returns)
    {

        var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
        var handlerPart = handlerMock
           .Protected()
           // Setup the PROTECTED method to mock
           .SetupSequence<Task<HttpResponseMessage>>(
              "SendAsync",
              ItExpr.IsAny<HttpRequestMessage>(),
              ItExpr.IsAny<CancellationToken>()
           );

        foreach (var item in returns)
        {
            handlerPart = AddReturnPart(handlerPart,item.Item1,item.Item2);
        }
        handlerMock.Verify();
        // use real http client with mocked handler here
        var httpClient = new HttpClient(handlerMock.Object)
        {
            BaseAddress = new Uri("http://test.com/"),
        };
        return httpClient;
    }

    private Moq.Language.ISetupSequentialResult<Task<HttpResponseMessage>> AdddReturnPart(Moq.Language.ISetupSequentialResult<Task<HttpResponseMessage>> handlerPart,
        HttpStatusCode statusCode, HttpContent content)
    {
        return handlerPart

           // prepare the expected response of the mocked http call
           .ReturnsAsync(new HttpResponseMessage()
           {
               StatusCode = statusCode, // HttpStatusCode.Unauthorized,
               Content = content //new StringContent("[{'id':1,'value':'1'}]"),
           });
    }
Run Code Online (Sandbox Code Playgroud)

调用上面的代码:

public void ExceuteMultipleHttpCalls()
{
            var contentSequence1 = new StringContent("{ 'id':'anId','email':'test@valid.com'}", Encoding.UTF8, "application/json");
            var contentSequence2 = new StringContent("{ 'id':'anotherId','email':'anotherTest@valid.com'}", Encoding.UTF8, "application/json");

            var sequenceResponse = new List<Tuple<HttpStatusCode, HttpContent>>
            {
                new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.OK, contentSequence1),
                new Tuple<HttpStatusCode, HttpContent>(HttpStatusCode.Created, contentSequence2)
            };

            HttpClient httpClient = GetHttpClientWithHttpMessageHandlerSequenceResponseMock(sequenceResponse);
//use this httpClient to call function where this client is called multiple times. 
Run Code Online (Sandbox Code Playgroud)

}