Мокать httpclient несколько раз в одном модульном тесте - PullRequest
1 голос
/ 13 февраля 2020

Привет, я пытаюсь издеваться над 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-вызов, но я не знаю, как его смутить. Это выглядит так:

...
...
            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-вызовов, которые нужно смоделировать. Как вы можете видеть, они отвечают разным контентом, поскольку они являются разными объектами и должны обрабатываться таким образом.

У кого-нибудь есть идеи, как это исправить?

1 Ответ

2 голосов
/ 13 февраля 2020

Иногда настройка макета не всегда может быть лучшим подходом. Поскольку 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

Клиент с пользовательским обработчиком теперь может вызываться по мере необходимости при проверке ожидаемого поведения испытуемого.

...