使用多个请求模拟HttpClient

4

我正在尝试对调用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;
}

到目前为止,一切顺利。我已经能够测试我的函数的一半,第二个部分会调用另一个API - 然后这两个响应被包装成一个由系统使用的对象。问题是,第二个API需要具有不同的模拟响应。
我认为我可以在上面的代码中更改 ItExpr.IsAny<HttpRequestMessage>()new HttpRequestMessage(HttpMethod.Post, "http://LiveUrl.com/AuthenticateUserCredential"),然后根据URI使用多个 Setup/Returns 更改响应,但是我尝试了以下内容(只有一个 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;
}

现在上述问题破坏了第一个 API 调用 - 我收到以下响应:

处理程序没有返回响应消息

现在我陷入困境 - 我试图做的事情是否可能?
6个回答

10

认为使用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'}]"),
           });
    }

调用上面的代码:

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. 

}


成功了!谢谢你! - Sudharshann D

1

一个调用API的函数无法进行单元测试,这不是单元测试,而是集成测试。如果想简化生活,请开始以功能方式思考。

你有一个调用API的函数,假设它看起来像这样:

public string DoSomething()
{
    string myData = //here we call another API
    //here we do something with the response from the API
}

现在,在你的函数之外进行API调用:
string myData = // api call
DoSomething(myData);

public string DoSomething(string myData)
    {
        // here we do something with myData 
        //this function no longer cares how it got the data.
    }

现在您可以对函数进行单元测试,而无需担心模拟任何内容,从而检查实际功能和业务规则。一切都变得更加简单。
您仍然可以使用集成测试来测试实际的API,这些测试将检查您是否获得正确的HTTP代码、异常、模型验证等等。

这似乎比尝试模拟httpclient的响应要简单得多...让我试一试。 - Percy

1

对于那些感兴趣的人,NSubstitute版本的模拟多个HTTP请求将像这样:

    var requestUri = "http://LiveUrl.com/AuthenticateUserCredential";
    
    var response = Task.FromResult(new HttpResponseMessage
    {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent(mockResponse)
    });
    var mockClient = Substitute.For<HttpClient>();
    
    mockClient.SendAsync(
        Arg.Is<HttpRequestMessage>(x => x.RequestUri.AbsoluteUri.StartsWith(requestUri)),
        Arg.Any<CancellationToken>())
       .Returns(response);

1
你可以这样做:

    var requestUri = "http://LiveUrl.com/AuthenticateUserCredential";

    var response = Task.FromResult(new HttpResponseMessage
    {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent(mockResponse)
    })

    mockMessageHandler
        .Protected()
        .Setup<Task<HttpResponseMessage>>("SendAsync", 
            ItExpr.Is<HttpRequestMessage>(x => x.RequestUri.AbsoluteUri.StartsWith(requestUri)),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(response);

1

不确定以前的版本是否允许这样做,但是如果您在同一个方法中有不同的HTTP调用,您可以通过指定HttpRequestMessage来对每个调用进行Moq,就像您所做的那样。与您的代码唯一的区别是,不是将其初始化为new

new HttpRequestMessage()

添加它
ItExpr.Is<HttpRequestMessage>(PREDICATE BY WHICH YOU WILL MATCH YOUR ROUTE)

例如:

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

    return mockMessageHandler;
}

然后您可以在此设置下方添加另一个设置,用于不同的URL或使用不同的HTTP方法。

0
使用RichardSzalay.MockHttp
var mockHttp = new MockHttpMessageHandler();
mockHttp.When(HttpMethod.Post, url1).Respond(response1);
mockHttp.When(HttpMethod.Get, url2).Respond(response2);
var client = mockHttp.ToHttpClient(); // pass this to your SUT

如果你使用HttpClient进行大量的测试,最终你也会以帮助工具的形式“重新发明”它。

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