Модульное тестирование HTTP-запросов в c #

Я пишу код, который вызывает веб-сервис, читает ответ и что-то делает с ним. Мой код выглядит номинально следующим образом:

string body = CreateHttpBody(regularExpression, strategy); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url); request.Method = "POST"; request.ContentType = "text/plain; charset=utf-8"; using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(Encoding.UTF8.GetBytes(body), 0, body.Length); requestStream.Flush(); } using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { byte[] data = new byte[response.ContentLength]; using (Stream stream = response.GetResponseStream()) { int bytesRead = 0; while (bytesRead < data.Length) { bytesRead += stream.Read(data, bytesRead, data.Length - bytesRead); } } return ExtractResponse(Encoding.UTF8.GetString(data)); } 

Единственные части, в которых я фактически выполняю произвольные манипуляции, находятся в ExtractResponse и CreateHttpBody . Тем не менее, он чувствует себя неправильно, просто проверяя эти методы, и надеемся, что остальная часть кода будет собрана правильно. Есть ли способ, которым я могу перехватить HTTP-запрос и подавать его вместо данных?

EDIT Эта информация устарела. Гораздо проще построить такой код, используя библиотеки System.Net.Http.HttpClient .

В коде вы не можете перехватывать вызовы в HttpWebRequest потому что вы создаете объект в том же методе. Если вы позволите другому объекту создать HttpWebRequest , вы можете передать объект-макет и использовать его для тестирования.

Поэтому вместо этого:

 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url); 

Использовать это:

 IHttpWebRequest request = this.WebRequestFactory.Create(_url); 

В вашем модульном тесте вы можете передать в WebRequestFactory который создает макет объекта.

Кроме того, вы можете разделить код чтения streamа в отдельной функции:

 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { byte[] data = ReadStream(response.GetResponseStream()); return ExtractResponse(Encoding.UTF8.GetString(data)); } 

Это позволяет тестировать ReadStream() отдельно.

Чтобы выполнить больше теста интеграции, вы можете настроить свой собственный HTTP-сервер, который возвращает тестовые данные, и передать URL-адрес этого сервера вашему методу.

Я бы, вероятно, начал с рефакторинга кода, чтобы сделать его более слабо связанным с фактическим HTTP-запросом. Прямо сейчас этот код, похоже, выполняет очень много.

Это можно сделать, введя абстракцию:

 public interface IDataRetriever { public byte[] RetrieveData(byte[] request); } 

Теперь class, который вы пытаетесь выполнить модульный тест, может быть отделен от фактического HTTP-запроса с использованием шаблона проектирования инверсии управления:

 public class ClassToTest { private readonly IDataRetriever _dataRetriever; public Foo(IDataRetriever dataRetriever) { _dataRetriever = dataRetriever; } public string MethodToTest(string regularExpression, string strategy) { string body = CreateHttpBody(regularExpression, strategy); byte[] result = _dataRetriever.RetrieveData(Encoding.UTF8.GetBytes(body)); return ExtractResponse(Encoding.UTF8.GetString(result)); } } 

Это больше не обязанность ClassToTest обрабатывать фактический HTTP-запрос. Теперь он отделен. Тестирование MethodToTest становится тривиальной задачей.

И последняя часть, очевидно, должна иметь реализацию абстракции, которую мы ввели:

 public class MyDataRetriever : IDataRetriever { private readonly string _url; public MyDataRetriever(string url) { _url = url; } public byte[] RetrieveData(byte[] request) { using (var client = new WebClient()) { client.Headers[HttpRequestHeader.ContentType] = "text/plain; charset=utf-8"; return client.UploadData(_url, request); } } } 

Затем вы можете настроить свою любимую инфраструктуру DI для вставки экземпляра MyDataRetriever в ClassToTest classа ClassToTest в вашем фактическом приложении.

Если высмеивать HttpWebRequest и HttpWebResponse становится слишком громоздким, или если вам когда-либо понадобится проверить код в приемочном тесте, где вы вызываете свой код из «снаружи», то создание фальшивого сервиса, вероятно, является лучшим способом.

Я на самом деле написал библиотеку с открытым исходным кодом под названием MockHttpServer, чтобы помочь с этим, что делает ее очень простой для издевательства над любыми внешними службами, которые обмениваются данными через HTTP.

Вот пример использования его с RestSharp для вызова конечной точки API с ним, но HttpWebRequest будет работать так же хорошо.

 using (new MockServer(3333, "/api/customer", (req, rsp, prm) => "Result Body")) { var client = new RestClient("http://localhost:3333/"); var result = client.Execute(new RestRequest("/api/customer", Method.GET)); } 

На странице GitHub есть довольно подробное чтение, в котором представлены все доступные для его использования опции, а сама библиотека доступна через NuGet.

Если вы с удовольствием перейдете на HttpClient (официальную, переносимую, http-клиентскую библиотеку), я написал библиотеку некоторое время назад, что может помочь вызвать MockHttp . Он обеспечивает свободный API, который позволяет предоставлять ответы на запросы, согласованные с использованием ряда атрибутов.

  • Очистка после всех тестов junit
  • Как проверить, что исключение не выбрасывается?
  • Mockito - разница между doReturn () и когда ()
  • Xcode 4: запустить тесты из командной строки (xcodebuild)?
  • Как предоставить файлы данных для тестов на андроид
  • Как обмануть ConfigurationManager.AppSettings с moq
  • Должны ли частные / защищенные методы проходить единичный тест?
  • Как игнорировать маркер порядка байтов UTF-8 в сравнении строк?
  • Mocking $ modal в модульных тестах AngularJS
  • Настройка IntelliJ IDEA для модульного тестирования с помощью JUnit
  • Может ли Google выманить метод с типом возвращаемого интеллектуального указателя?
  • Interesting Posts

    Как получить цвет из шестнадцатеричного цветового кода с помощью .NET?

    Кросс-платформенный способ изменения приоритета Java-процесса

    Передача агента SSH с использованием разных имен пользователей и разных ключей

    Передача объекта между компонентами @ViewScoped без использования параметров GET

    Как завершить текущую работу в Android

    Настройка широкого монитора в XP

    Что такое внутреннее представление Java для String? Изменен UTF-8? UTF-16?

    Сортировка по строке, которая может содержать число

    UWP-приложение для размытия в реальном времени с использованием DX Compositor

    Пользовательский список

    Как удалить определенный текст в нескольких ячейках сразу?

    Код структуры Entity сначала удаляется с помощью каскада

    Как работать с XML в C #

    Есть ли разница в производительности между «let» и «var» в JavaScript

    Процесс Vim останавливается после выполнения внешней команды

    Давайте будем гением компьютера.