Модульное тестирование 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 .
- Проекты NUnit vs Visual Studio 2008 для тестирования модhive?
- Измельчение базы данных в node.js?
- Почему свойство, которое я хочу высмеять, должно быть виртуальным?
- Mockito: InvalidUseOfMatchersException
- Каков наилучший способ модульного тестирования кода Objective-C?
- Модульный ввод / вывод файлов
- Несколько операторов RunWith в jUnit
- Переопределение привязки в Guice
- Группировка тестов JUnit
- Модульное тестирование кода C ++ - Инструменты и методология
- Как я могу написать единичный тест, чтобы определить, можно ли собирать мусор?
- Спекуляция для JUnit XML Output
- Жасмин: обратный вызов Async не вызывался в течение таймаута, указанного jasmine.DEFAULT_TIMEOUT_INTERVAL
В коде вы не можете перехватывать вызовы в 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, который позволяет предоставлять ответы на запросы, согласованные с использованием ряда атрибутов.