在单个单元测试中多次模拟 httpclient

Mocking httpclient multiple times in a single Unit Test

您好,我正在尝试模拟 httpclient,显然是强制它给我需要的结果。 问题 是,在我的单元测试中,我必须模拟 httpClient 两次,每次都有不同的响应。

...
...
            var httpReq = new HttpRequestMessage(
            HttpMethod.Get,
            $"{config["MY_TEST_ENDPOINT"]/site1}");

            var myContent = await response.Content.ReadAsStringAsync();
            var myFirstModel = JsonConvert.DeserializeObject<MyFirstModel>(myContent );
...
...

我嘲笑这个可能:

...
myMockedHttp
            .Protected()
            .Setup<Task<HttpResponseMessage>>(
                "SendAsync",
                ItExpr.IsAny<HttpRequestMessage>(),
                ItExpr.IsAny<CancellationToken>())
            .ReturnsAsync(new HttpResponseMessage()
            {
                StatusCode = HttpStatusCode.OK,
                Content = new StringContent("[{'id':'0001'," +
                                            "'name':'Jonah'," +
                                            "'street':'Cambridge Street 1234'}]"
                                            )
            })
            .Verifiable();

这会给我一个响应,我可以反序列化并使用它 - 给它一个包含 3 个成员的对象:id、name 和 street。

我的问题来了 - 我也在同一个单元测试中有另一个 httpclient-call,但我不知道如何模拟它。它是这样的:

...
...
            var httpReq = new HttpRequestMessage(
            HttpMethod.Get,
            $"{config["MY_TEST_ENDPOINT"]/site2}");

            var httpResponseMsg = await _httpClient.SendAsync(httpReq);

            var myContent = await response.Content.ReadAsStringAsync();
            var myFirstModel = JsonConvert.DeserializeObject<MySecondModel>(myContent );
...
...

最初 - 如果它是唯一的 httpclient 调用,我会像这样模拟它:

...
myMockedHttp
            .Protected()
            .Setup<Task<HttpResponseMessage>>(
                "SendAsync",
                ItExpr.IsAny<HttpRequestMessage>(),
                ItExpr.IsAny<CancellationToken>())
            .ReturnsAsync(new HttpResponseMessage()
            {
                StatusCode = HttpStatusCode.OK,
                Content = new StringContent("[{'id':'34'," +
                                            "'insurance':'Etx'," +
                                            "'insider':'daily'," +
                                            "'collectives':'4'}]"
                                            )
            })
            .Verifiable();
...
...

但显然我不能...问题是,我从未尝试过需要模拟多个 httpclient 调用的情况。如您所见,它们响应不同的内容,因为它们是不同的对象,需要以这种方式处理。

有人知道如何解决这个问题吗?

有时设置模拟可能并不总是最好的方法。由于 HttpClient 确实依赖于 HttpMessageHandler,您可以创建一个简单的来处理预期的请求

class TestMessageHandler : HttpMessageHandler {
    private readonly IDictionary<string, HttpResponseMessage> messages;

    public TestMessageHandler(IDictionary<string, HttpResponseMessage> messages) {
        this.messages = messages;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        var response = new HttpResponseMessage(HttpStatusCode.NotFound);
        if (messages.ContainsKey(request.RequestUri.ToString()))
            response = messages[request.RequestUri.ToString()] ?? new HttpResponseMessage(HttpStatusCode.NoContent);
        response.RequestMessage = request;
        return Task.FromResult(response);
    }
}

上面的处理程序只是使用传入请求的 URL 在字典中查找响应。如果需要,它可以重构以处理更复杂的请求,但对于简单的请求,这将很好地服务。

将处理程序传递给为测试创建的客户端

public async Task MyTestMethod() {
    //Arrange
    Dictionary<string, HttpResponseMessage> messages = new Dictionary<string, HttpResponseMessage>();
    messages.Add("https://somesite1.com/ping", new HttpResponseMessage() {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent("[{'id':'0001'," +
                                    "'name':'Jonah'," +
                                    "'street':'Cambridge Street 1234'}]"
                                    )
    });
    messages.Add("https://somesite2.com/ping", new HttpResponseMessage() {
        StatusCode = HttpStatusCode.OK,
        Content = new StringContent("[{'id':'34'," +
                                    "'insurance':'Etx'," +
                                    "'insider':'daily'," +
                                    "'collectives':'4'}]"
                                    )
    });

    var client = new HttpClient(new TestMessageHandler(messages));

    //...inject client as needed

在验证被测对象的预期行为时,现在可以根据需要调用带有自定义处理程序的客户端。