У меня есть некоторые проблемы с попыткой обернуть мой код для использования в модульных тестах. Проблема вот в чем. Имею интерфейс IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
И класс, использующий его, HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
А затем класс Connection, который использует simpleIOC для внедрения клиентской реализации:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
И затем у меня есть проект модульного тестирования, в котором есть этот класс:
private IHttpHandler _httpClient;
[TestMethod]
public void TestMockConnection()
{
var client = new Connection(_httpClient);
client.doSomething();
// Here I want to somehow create a mock instance of the http client
// Instead of the real one. How Should I approach this?
}
Теперь, очевидно, у меня будут методы в классе Connection, которые будут извлекать данные (JSON) из моего бэкенда. Однако я хочу писать модульные тесты для этого класса, и, очевидно, я не хочу писать тесты для реальной серверной части, а скорее для имитации. Я без особого успеха пытался найти в Google хороший ответ на этот вопрос. Я мог и раньше использовал Moq для издевательств, но никогда не использовал что-то вроде httpClient. Как мне подойти к этой проблеме?
Заранее спасибо.
источник
HttpClient
Проблема заключается в выставлении a в вашем интерфейсе. Вы заставляете своего клиента использоватьHttpClient
конкретный класс. Вместо этого, вы должны разоблачить абстракции изHttpClient
.Ответы:
Ваш интерфейс раскрывает конкретную
HttpClient
класс, поэтому любые классы, использующие этот интерфейс, привязаны к нему, это означает, что над ним нельзя издеваться.HttpClient
не наследуется ни от одного интерфейса, поэтому вам придется написать свой собственный. Я предлагаю шаблон в стиле декоратора :И ваш класс будет выглядеть так:
Смысл всего этого в том, что он
HttpClientHandler
создает свой собственныйHttpClient
, затем вы, конечно, можете создать несколько классов, которые реализуютIHttpHandler
по-разному.Основная проблема с этим подходом заключается в том, что вы эффективно пишете класс, который просто вызывает методы в другом классе, однако вы можете создать класс, который наследуется от
HttpClient
(см . Пример Nkosi , это гораздо лучший подход, чем мой). Жизнь была бы намного проще, если быHttpClient
у вас был интерфейс, который можно было бы имитировать, но, к сожалению, это не так.Однако этот пример - не золотой билет.
IHttpHandler
по-прежнему полагается наHttpResponseMessage
, который принадлежитSystem.Net.Http
пространству имен, поэтому, если вам нужны другие реализации, кромеHttpClient
, вам придется выполнить какое-то сопоставление, чтобы преобразовать их ответы вHttpResponseMessage
объекты. Это, конечно, только проблема , если вам нужно использовать несколько реализаций изIHttpHandler
но это не выглядит , как вы делаете так , что это не конец света, но это что - то думать.В любом случае, вы можете просто имитировать,
IHttpHandler
не беспокоясь о конкретномHttpClient
классе, поскольку он был абстрагирован.Я рекомендую тестировать неасинхронные методы, так как они по-прежнему вызывают асинхронные методы, но без проблем, связанных с асинхронными методами модульного тестирования, см. Здесь
источник
Расширяемость HttpClient заключается в
HttpMessageHandler
передаче конструктору. Его цель - разрешить реализацию для конкретной платформы, но вы также можете имитировать его. Нет необходимости создавать оболочку декоратора для HttpClient.Если вы предпочитаете DSL использовать Moq, у меня есть библиотека на GitHub / Nuget, которая немного упрощает работу: https://github.com/richardszalay/mockhttp
источник
var client = new HttpClient()
сvar client = ClientFactory()
и настройка поляinternal static Func<HttpClient> ClientFactory = () => new HttpClient();
и на уровне теста вы можете переписать это поле.HttpClientFactory
как вFunc<HttpClient>
. Поскольку я рассматриваю HttpClient исключительно как деталь реализации, а не как зависимость, я буду использовать статику, как показано выше. Меня полностью устраивают тесты, управляющие внутренними устройствами. Если меня волнует чистый изм, я выставлю полные серверы и протестирую пути к живому коду. Использование любых имитаций означает, что вы принимаете приблизительное поведение, а не реальное поведение.Я согласен с некоторыми другими ответами, что лучший подход - издеваться над HttpMessageHandler, а не оборачивать HttpClient. Этот ответ уникален тем, что он по-прежнему вводит HttpClient, что позволяет ему быть одноэлементным или управляемым с помощью инъекции зависимостей.
( Источник ).
Мокинг HttpMessageHandler может быть немного сложным, потому что SendAsync защищен. Вот полный пример с использованием xunit и Moq.
источник
mockMessageHandler.Protected()
был убийца. Спасибо за этот пример. Это позволяет писать тест, вообще не изменяя исходный код..ReturnsAsync(new HttpResponseMessage {StatusCode = HttpStatusCode.OK, Content = new StringContent(testContent)})
Это частый вопрос, и я был сильно на стороне, желая иметь возможность издеваться над HttpClient, но я думаю, что наконец пришел к осознанию того, что вы не должны высмеивать HttpClient. Это кажется логичным, но я думаю, что нам промыли мозги тем, что мы видим в библиотеках с открытым исходным кодом.
Мы часто видим «клиентов» там, где мы издеваемся в нашем коде, чтобы мы могли тестировать изолированно, поэтому мы автоматически пытаемся применить тот же принцип к HttpClient. HttpClient действительно многое делает; вы можете думать об этом как о менеджере для HttpMessageHandler, поэтому вы не хотите высмеивать его, и поэтому у него до сих пор нет интерфейса. Часть, которая вас действительно интересует для модульного тестирования или даже проектирования ваших сервисов, - это HttpMessageHandler, поскольку именно он возвращает ответ, и вы можете имитировать это.
Также стоит отметить, что вам, вероятно, следует начать относиться к HttpClient как к большему делу. Например: Сведите к минимуму установку новых HttpClients. Используйте их повторно, они предназначены для повторного использования и потребляют гораздо меньше ресурсов, если вы это сделаете. Если вы начнете относиться к этому как к более крупной сделке, вам будет гораздо хуже, если вы захотите высмеять его, и теперь обработчик сообщений станет тем, что вы вводите, а не клиентом.
Другими словами, создавайте свои зависимости вокруг обработчика, а не клиента. Еще лучше, абстрактные «службы», использующие HttpClient, которые позволяют вам внедрить обработчик и вместо этого использовать его как вашу инъекционную зависимость. Затем в своих тестах вы можете подделать обработчик, чтобы контролировать ответ для настройки ваших тестов.
Оборачивание HttpClient - безумная трата времени.
Обновление: см. Пример Джошуа Дума. Это именно то, что я рекомендую.
источник
Как также упоминалось в комментариях, вам нужно абстрагироваться от
HttpClient
, чтобы не быть связан с ним. Я делал нечто подобное в прошлом. Я постараюсь адаптировать то, что я сделал, к тому, что вы пытаетесь сделать.Сначала посмотрите на
HttpClient
класс и решите, какие функции он предоставляет, что может потребоваться.Вот возможность:
Опять же, как было сказано ранее, это было сделано для определенных целей. Я полностью абстрагировал большинство зависимостей от всего, с чем имел дело,
HttpClient
и сосредоточился на том, что я хотел вернуть. Вы должны оценить, как вы хотите абстрагироваться отHttpClient
чтобы обеспечить только необходимую вам функциональность.Теперь это позволит вам высмеивать только то, что необходимо протестировать.
Я бы даже рекомендовал
IHttpHandler
полностью отказаться от этого и использоватьHttpClient
абстракциюIHttpClient
. Но я просто не выбираю, поскольку вы можете заменить тело интерфейса вашего обработчика членами абстрактного клиента.Затем реализация
IHttpClient
может быть использована для обертывания / адаптации реального / конкретногоHttpClient
или любого другого объекта в этом отношении, который может использоваться для выполнения HTTP-запросов, поскольку то, что вы действительно хотели, было услугой, которая предоставляла эту функциональность, а неHttpClient
конкретно. Использование абстракции - это чистый (Мое мнение) и твердый подход, который может сделать ваш код более удобным в обслуживании, если вам нужно переключить базовый клиент на что-то еще по мере изменения структуры.Вот фрагмент того, как можно реализовать реализацию.
Как вы можете видеть в приведенном выше примере, большая часть тяжелой работы, обычно связанной с использованием
HttpClient
, скрыта за абстракцией.Затем вы можете ввести класс подключения с помощью абстрактного клиента
Затем ваш тест может имитировать то, что необходимо для вашей SUT.
источник
HttpClient
и адаптер для создания вашего конкретного экземпляра с использованиемHttpClientFactory
. Это делает тестирование логики, выходящей за рамки HTTP-запроса, тривиальным, что и является нашей целью.Основываясь на других ответах, я предлагаю этот код, который не имеет никаких внешних зависимостей:
источник
HttpMessageHandler
самостоятельно, делает это практически невозможным - и вы должны это делать, потому что методы естьprotected internal
.Я думаю, проблема в том, что у вас он немного перевернут.
Если вы посмотрите на класс выше, я думаю, что это то, что вам нужно. Microsoft рекомендует поддерживать работоспособность клиента для оптимальной производительности, поэтому такой тип структуры позволяет это делать. Кроме того, HttpMessageHandler является абстрактным классом и, следовательно, может быть издевательским. Тогда ваш метод тестирования будет выглядеть так:
Это позволяет вам проверить свою логику, высмеивая поведение HttpClient.
Извините, ребята, после того, как я написал это и попробовал себя, я понял, что вы не можете издеваться над защищенными методами HttpMessageHandler. Впоследствии я добавил следующий код, чтобы разрешить внедрение правильного макета.
Тогда тесты, написанные с этим, выглядят примерно так:
источник
Один из моих коллег заметил, что большинство
HttpClient
методов вызываютсяSendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
изнутри, что является виртуальным методомHttpMessageInvoker
:Итак, самый простой способ издеваться
HttpClient
- это просто издеваться над конкретным методом:и ваш код может вызывать большинство (но не все)
HttpClient
методов класса, включая обычныйОтметьте здесь, чтобы подтвердить https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
источник
SendAsync(HttpRequestMessage)
напрямую. Если вы можете изменить свой код, чтобы не использовать эту удобную функцию, то имитация HttpClient напрямую путем переопределенияSendAsync
на самом деле является самым чистым решением, которое я нашел.Альтернативой может быть установка HTTP-сервера-заглушки, который возвращает стандартные ответы на основе шаблона, соответствующего URL-адресу запроса, что означает, что вы тестируете настоящие HTTP-запросы, а не имитации. Исторически это потребовало значительных усилий при разработке и было бы слишком медленным для рассмотрения для модульного тестирования, однако библиотека OSS WireMock.net проста в использовании и достаточно быстра, чтобы запускаться с большим количеством тестов, поэтому, возможно, стоит подумать. Настройка - это несколько строк кода:
Вы можете найти более подробную информацию и руководство по использованию Wiremock в тестах здесь.
источник
Вот простое решение, которое мне понравилось.
Использование библиотеки moq mocking.
Источник: https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
источник
SendAsync
все равно используются, поэтому дополнительных настроек не требуется.Многие ответы меня не убеждают.
Прежде всего, представьте, что вы хотите провести модульное тестирование метода, который использует
HttpClient
. Вы не должны создавать экземплярыHttpClient
непосредственно в своей реализации. Вы должны внедрить factory с ответственностью за предоставление вам экземпляраHttpClient
. Таким образом, вы можете позже смоделировать эту фабрику и вернуть то, чтоHttpClient
захотите (например, макет,HttpClient
а не настоящий).Итак, у вас будет такая фабрика:
И реализация:
Конечно, вам нужно будет зарегистрировать эту реализацию в своем контейнере IoC. Если вы используете Autofac, это будет примерно так:
Теперь у вас будет правильная и проверяемая реализация. Представьте, что ваш метод выглядит примерно так:
Теперь тестовая часть.
HttpClient
extendsHttpMessageHandler
, что является абстрактным. Создадим "макет"HttpMessageHandler
что принимает делегат, чтобы при использовании макета мы также могли настроить каждое поведение для каждого теста.И теперь, с помощью Moq (и FluentAssertions, библиотеки, которая делает модульные тесты более удобочитаемыми), у нас есть все необходимое для модульного тестирования нашего метода PostAsync, который использует
HttpClient
Очевидно, что этот тест глупый, и мы действительно тестируем наш макет. Но Вы получаете идею. Вы должны протестировать осмысленную логику в зависимости от вашей реализации, например ..
Целью этого ответа было проверить что-то, что использует HttpClient, и это хороший чистый способ сделать это.
источник
Присоединиться к вечеринке немного поздно, но мне нравится использовать Wiremocking ( https://github.com/WireMock-Net/WireMock.Net ), когда это возможно, при тестировании интеграции микросервиса ядра dotnet с зависимостями REST ниже по течению.
Реализуя TestHttpClientFactory, расширяющий IHttpClientFactory, мы можем переопределить метод
HttpClient CreateClient (имя строки)
Таким образом, при использовании именованных клиентов в вашем приложении вы контролируете возвращение HttpClient, подключенного к вашему wiremock.
Хорошая вещь в этом подходе заключается в том, что вы ничего не меняете в тестируемом приложении и разрешаете интеграционные тесты курса, выполняя фактический REST-запрос к вашей службе и имитируя json (или что-то еще), который должен возвращать фактический нисходящий запрос. Это приводит к кратким тестам и минимальному количеству насмешек в вашем приложении.
и
источник
Поскольку
HttpClient
использоватьSendAsync
метод для выполнения всегоHTTP Requests
, вы можетеoverride SendAsync
метод и издеваться надHttpClient
.Для создания обертки
HttpClient
в ainterface
, как показано нижеЗатем используйте выше
interface
для внедрения зависимостей в свой сервис, образец нижеТеперь в проекте модульного теста создайте вспомогательный класс для издевательства
SendAsync
. ЭтоFakeHttpResponseHandler
класс,inheriting
DelegatingHandler
который предоставит возможность переопределитьSendAsync
метод. После переопределенияSendAsync
метода необходимо настроить ответ для каждогоHTTP Request
вызывающегоSendAsync
метода, для этого создайте сDictionary
помощьюkey
asUri
иvalue
as,HttpResponseMessage
чтобы всякий раз, когда естьHTTP Request
и еслиUri
совпаденияSendAsync
, возвращали настроенныйHttpResponseMessage
.Создайте новую реализацию с
IServiceHelper
помощью mocking framework или как показано ниже. ЭтотFakeServiceHelper
класс мы можем использовать для внедренияFakeHttpResponseHandler
класса, чтобы каждый раз, когда он былHttpClient
создан,class
он использовалFakeHttpResponseHandler class
вместо фактической реализации.И в тесте настройте
FakeHttpResponseHandler class
, добавивUri
и ожидаемыйHttpResponseMessage
. ОнUri
должен быть фактическойservice
конечной точкой,Uri
чтобы приoverridden SendAsync
вызове метода из фактическойservice
реализации он совпадал сUri
inDictionary
и отвечал настроеннымHttpResponseMessage
. После настройки введитеFakeHttpResponseHandler object
поддельнуюIServiceHelper
реализацию. Затем вставьте вFakeServiceHelper class
фактическую службу, которая заставит фактическую службу использоватьoverride SendAsync
метод.Ссылка на GitHub: есть образец реализации
источник
Вы можете использовать библиотеку RichardSzalay MockHttp, которая имитирует HttpMessageHandler и может возвращать объект HttpClient, который будет использоваться во время тестов.
GitHub MockHttp
PM> Установочный пакет RichardSzalay.MockHttp
Из документации GitHub
Пример с GitHub
источник
Это старый вопрос, но я чувствую желание расширить ответы на решение, которого я здесь не видел.
Вы можете подделать сборку Microsoft (System.Net.Http), а затем использовать ShinsContext во время теста.
В зависимости от вашей реализации и тестирования, я бы предложил реализовать все желаемые действия, когда вы вызываете метод в HttpClient и хотите подделать возвращаемое значение. Использование ShimHttpClient.AllInstances подделает вашу реализацию во всех экземплярах, созданных во время вашего теста. Например, если вы хотите подделать метод GetAsync (), сделайте следующее:
источник
Я сделал что-то очень простое, поскольку был в среде DI.
и макет:
источник
Все, что вам нужно, это тестовая версия
HttpMessageHandler
класса, которую вы передаетеHttpClient
ctor. Главное, чтобы у вашего тестовогоHttpMessageHandler
класса былHttpRequestHandler
делегат, который вызывающие абоненты могут установить и просто обрабатыватьHttpRequest
так, как они хотят.Вы можете использовать экземпляр этого класса для создания конкретного экземпляра HttpClient. С помощью делегата HttpRequestHandler вы полностью контролируете исходящие HTTP-запросы от HttpClient.
источник
Вдохновленный ответом PointZeroTwo , вот образец с использованием NUnit и FakeItEasy .
SystemUnderTest
в этом примере - это класс, который вы хотите протестировать - для него не приводится образец содержимого, но я предполагаю, что он у вас уже есть!источник
Возможно, в вашем текущем проекте нужно будет изменить какой-то код, но для новых проектов вы обязательно должны рассмотреть возможность использования Flurl.
https://flurl.dev
Это клиентская библиотека HTTP для .NET с плавным интерфейсом, специально обеспечивающим возможность тестирования кода, который использует ее для выполнения HTTP-запросов.
На веб-сайте есть множество примеров кода, но вкратце вы используете это в своем коде.
Добавьте использование.
Отправьте запрос на получение и прочтите ответ.
В модульных тестах Flurl действует как имитация, которую можно настроить для работы по желанию, а также для проверки выполненных вызовов.
источник
После тщательного поиска я нашел лучший способ добиться этого.
источник
Чтобы добавить свои 2 цента. Чтобы имитировать определенные методы HTTP-запроса, используйте Get или Post. Это сработало для меня.
источник