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

  • Отфильтровать отчеты о покрытиях JaCoCo с помощью Gradle
  • Как обмануть ConfigurationManager.AppSettings с moq
  • Как я могу написать единичный тест, чтобы определить, можно ли собирать мусор?
  • Как запустить NUnit в режиме отладки из Visual Studio?
  • Как издеваться над новой датой () в java с помощью Mockito
  • Записи classа Eclipse, используемые только для тестов
  • Единичное тестирование DbContext
  • Как вы издеваетесь над коллекцией объектов сеанса с помощью Moq
  • Поддельный DbContext для платформы Entity Framework 4.1 для тестирования
  • Первая архитектура ASP.NET MVC3 и Entity Framework Code
  • Очистка после всех тестов junit
  • Давайте будем гением компьютера.