Я наконец-то начал возиться с созданием некоторых приложений, которые работают с веб-интерфейсами RESTful, однако меня беспокоит, что я забиваю их серверы каждый раз, когда нажимаю F5, чтобы запустить серию тестов.
По сути, мне нужно получить серию веб-ответов, чтобы я мог проверить, правильно ли я разбираю различные ответы, вместо того, чтобы каждый раз попадать на их серверы. Я думал, что могу сделать это один раз, сохранить XML и затем работать локально.
Однако я не понимаю, как я могу "издеваться" над WebResponse, поскольку (AFAIK) они могут быть созданы только с помощью WebRequest.GetResponse
Как вы, ребята, издеваетесь над такими вещами? Ты? Мне просто не нравится, что я забиваю их серверы: S Я не хочу сильно менять код тоже, но я думаю, что есть элегантный способ сделать это ...
Ответом Уилла была пощечина, в которой я нуждался, я знал, что упускаю главное!
Как только код будет готов, я вставлю несколько примеров.
Привет, Ник, как в обновлении. В итоге я начал программировать с использованием интерфейса. Затем я реализую интерфейс дважды. Тот, который возвращает статический контент, тот, который фактически использует WebRequest. Затем вы можете UT потребителя, а затем просто использовать фактический сейф, зная, что вы протестировали класс потребителя. Всегда помните: «не тестируйте чужой код» (например, WebRequest).





Вы не можете. Лучше всего обернуть его в прокси-объект, а затем поиздеваться над ним. В качестве альтернативы вам придется использовать фиктивный фреймворк, который может перехватывать типы, которые нельзя имитировать, например TypeMock. Но вы говорите о долларах. Лучше сделать небольшое обертывание.
Видимо вы может немного поработали. Отметьте здесь ответ, получивший наибольшее количество голосов.
Не могли бы вы пояснить, как я могу "обернуть", не затрагивая основной код? Как только этот GetResponse будет вызван, бум! он работает ... Очевидно, мне нужно сделать некоторую инъекцию зависимости, но я не понимаю, как я могу добраться до нужного мне объекта, понимаете?
Не сомневайся, будучи идиотом. Я с тобой. Создайте прокси-объект для передачи результата, интерфейс для вызовов. Реализовать в двух классах: 1 = WebRequest, 2 = Статические объекты). Теперь это имеет смысл, спасибо, что ударил меня по лицу и заставил меня увидеть смысл, я знал, что не могу видеть лес сквозь деревья :)
это неверно, поскольку ответ Ричарда Уиллиса ниже показывает, как это можно сделать. WebResponse МОЖЕТ быть смоделирован с помощью кода, на который он ссылается.
@tmont интересно и +1 Уиллису. Отредактировано для ясности. Хорошо, что они это хорошо спрятали ...
Это не идеальное решение, но оно работало у меня раньше и заслуживает особой заботы о простоте:
Также пример typemock задокументирован в форумы typemock:
using System;
using System.IO;
using System.Net;
using NUnit.Framework;
using TypeMock;
namespace MockHttpWebRequest
{
public class LibraryClass
{
public string GetGoogleHomePage()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.google.com");
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
return reader.ReadToEnd();
}
}
}
[TestFixture]
[VerifyMocks]
public class UnitTests
{
private Stream responseStream = null;
private const string ExpectedResponseContent = "Content from mocked response.";
[SetUp]
public void SetUp()
{
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] contentAsBytes = encoding.GetBytes(ExpectedResponseContent);
this.responseStream = new MemoryStream();
this.responseStream.Write(contentAsBytes, 0, contentAsBytes.Length);
this.responseStream.Position = 0;
}
[TearDown]
public void TearDown()
{
if (responseStream != null)
{
responseStream.Dispose();
responseStream = null;
}
}
[Test(Description = "Mocks a web request using natural mocks.")]
public void NaturalMocks()
{
HttpWebRequest mockRequest = RecorderManager.CreateMockedObject<HttpWebRequest>(Constructor.Mocked);
HttpWebResponse mockResponse = RecorderManager.CreateMockedObject<HttpWebResponse>(Constructor.Mocked);
using (RecordExpectations recorder = RecorderManager.StartRecording())
{
WebRequest.Create("http://www.google.com");
recorder.CheckArguments();
recorder.Return(mockRequest);
mockRequest.GetResponse();
recorder.Return(mockResponse);
mockResponse.GetResponseStream();
recorder.Return(this.responseStream);
}
LibraryClass testObject = new LibraryClass();
string result = testObject.GetGoogleHomePage();
Assert.AreEqual(ExpectedResponseContent, result);
}
[Test(Description = "Mocks a web request using reflective mocks.")]
public void ReflectiveMocks()
{
Mock<HttpWebRequest> mockRequest = MockManager.Mock<HttpWebRequest>(Constructor.Mocked);
MockObject<HttpWebResponse> mockResponse = MockManager.MockObject<HttpWebResponse>(Constructor.Mocked);
mockResponse.ExpectAndReturn("GetResponseStream", this.responseStream);
mockRequest.ExpectAndReturn("GetResponse", mockResponse.Object);
LibraryClass testObject = new LibraryClass();
string result = testObject.GetGoogleHomePage();
Assert.AreEqual(ExpectedResponseContent, result);
}
}
}
Я нашел этот вопрос, пытаясь сделать то же самое. Ничего не мог найти, но, покопавшись, обнаружил, что .Net Framework имеет встроенную поддержку для этого.
Вы можете зарегистрировать фабричный объект с помощью WebRequest.RegisterPrefix, который будет вызывать WebRequest.Create при использовании этого префикса (или URL-адреса). Объект фабрики должен реализовывать IWebRequestCreate, который имеет единственный метод Create, который возвращает WebRequest. Здесь вы можете вернуть свой макет WebRequest.
Я поместил образец кода на http://blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/
Это путь, пока ваш клиентский код не начнет проверять HttpWebResponse для просмотра кодов состояния ... тогда вам нужно увидеть мой ответ ниже!
Единственное, чего я до сих пор не могу понять, это как обрабатывать два последовательных веб-запроса ...
Ранее я нашел следующий блог, в котором объясняется довольно хороший подход с использованием Microsoft Moles.
http://maraboustork.co.uk/index.php/2011/03/mocking-httpwebresponse-with-moles/
Короче говоря, решение предлагает следующее:
[TestMethod]
[HostType("Moles")]
[Description("Tests that the default scraper returns the correct result")]
public void Scrape_KnownUrl_ReturnsExpectedValue()
{
var mockedWebResponse = new MHttpWebResponse();
MHttpWebRequest.AllInstances.GetResponse = (x) =>
{
return mockedWebResponse;
};
mockedWebResponse.StatusCodeGet = () => { return HttpStatusCode.OK; };
mockedWebResponse.ResponseUriGet = () => { return new Uri("http://www.google.co.uk/someRedirect.aspx"); };
mockedWebResponse.ContentTypeGet = () => { return "testHttpResponse"; };
var mockedResponse = "<html> \r\n" +
" <head></head> \r\n" +
" <body> \r\n" +
" <h1>Hello World</h1> \r\n" +
" </body> \r\n" +
"</html>";
var s = new MemoryStream();
var sw = new StreamWriter(s);
sw.Write(mockedResponse);
sw.Flush();
s.Seek(0, SeekOrigin.Begin);
mockedWebResponse.GetResponseStream = () => s;
var scraper = new DefaultScraper();
var retVal = scraper.Scrape("http://www.google.co.uk");
Assert.AreEqual(mockedResponse, retVal.Content, "Should have returned the test html response");
Assert.AreEqual("http://www.google.co.uk/someRedirect.aspx", retVal.FinalUrl, "The finalUrl does not correctly represent the redirection that took place.");
}
Вот решение, которое не требует насмешек. Вы реализуете все три компонента WebRequest: IWebRequestCreate, WebRequest и WebResponse. Смотри ниже. Мой пример генерирует неудачные запросы (бросая WebException), но должен иметь возможность адаптировать его для отправки «реальных» ответов:
class WebRequestFailedCreate : IWebRequestCreate {
HttpStatusCode status;
String statusDescription;
public WebRequestFailedCreate(HttpStatusCode hsc, String sd) {
status = hsc;
statusDescription = sd;
}
#region IWebRequestCreate Members
public WebRequest Create(Uri uri) {
return new WebRequestFailed(uri, status, statusDescription);
}
#endregion
}
class WebRequestFailed : WebRequest {
HttpStatusCode status;
String statusDescription;
Uri itemUri;
public WebRequestFailed(Uri uri, HttpStatusCode status, String statusDescription) {
this.itemUri = uri;
this.status = status;
this.statusDescription = statusDescription;
}
WebException GetException() {
SerializationInfo si = new SerializationInfo(typeof(HttpWebResponse), new System.Runtime.Serialization.FormatterConverter());
StreamingContext sc = new StreamingContext();
WebHeaderCollection headers = new WebHeaderCollection();
si.AddValue("m_HttpResponseHeaders", headers);
si.AddValue("m_Uri", itemUri);
si.AddValue("m_Certificate", null);
si.AddValue("m_Version", HttpVersion.Version11);
si.AddValue("m_StatusCode", status);
si.AddValue("m_ContentLength", 0);
si.AddValue("m_Verb", "GET");
si.AddValue("m_StatusDescription", statusDescription);
si.AddValue("m_MediaType", null);
WebResponseFailed wr = new WebResponseFailed(si, sc);
Exception inner = new Exception(statusDescription);
return new WebException("This request failed", inner, WebExceptionStatus.ProtocolError, wr);
}
public override WebResponse GetResponse() {
throw GetException();
}
public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) {
Task<WebResponse> f = Task<WebResponse>.Factory.StartNew (
_ =>
{
throw GetException();
},
state
);
if (callback != null) f.ContinueWith((res) => callback(f));
return f;
}
public override WebResponse EndGetResponse(IAsyncResult asyncResult) {
return ((Task<WebResponse>)asyncResult).Result;
}
}
class WebResponseFailed : HttpWebResponse {
public WebResponseFailed(SerializationInfo serializationInfo, StreamingContext streamingContext)
: base(serializationInfo, streamingContext) {
}
}
Вы должны создать подкласс HttpWebResponse, потому что иначе вы не сможете его создать.
Сложная часть (в методе GetException()) - это ввод значений, которые вы не можете переопределить, например StatusCode, и здесь на помощь приходит наш лучший друг SerializaionInfo! Здесь вы указываете значения, которые не можете переопределить. Очевидно, переопределите части (HttpWebResponse), которые вы можете, чтобы получить остальную часть пути.
Как я получил "имена" во всех этих вызовах AddValue()? Из сообщений об исключении! Было достаточно приятно рассказывать мне все по очереди, пока я не сделал это счастливым.
Теперь компилятор будет жаловаться на "устаревшее", но это, тем не менее, работает, включая .NET Framework версии 4.
Вот (проходящий) тестовый пример для справки:
[TestMethod, ExpectedException(typeof(WebException))]
public void WebRequestFailedThrowsWebException() {
string TestURIProtocol = TestContext.TestName;
var ResourcesBaseURL = TestURIProtocol + "://resources/";
var ContainerBaseURL = ResourcesBaseURL + "container" + "/";
WebRequest.RegisterPrefix(TestURIProtocol, new WebRequestFailedCreate(HttpStatusCode.InternalServerError, "This request failed on purpose."));
WebRequest wr = WebRequest.Create(ContainerBaseURL);
try {
WebResponse wrsp = wr.GetResponse();
using (wrsp) {
Assert.Fail("WebRequest.GetResponse() Should not have succeeded.");
}
}
catch (WebException we) {
Assert.IsInstanceOfType(we.Response, typeof(HttpWebResponse));
Assert.AreEqual(HttpStatusCode.InternalServerError, (we.Response as HttpWebResponse).StatusCode, "Status Code failed");
throw we;
}
}
Это действительно был отличный способ «подделать» веб-ответ для целей модульного тестирования до .net 4.6, но он не работает в .netstandard, потому что «устаревший» конструктор был удален.
Привет, Роб, у тебя это сработало? Я только что наткнулся на это, и, как новичок в модульном тестировании, мне бы хотелось узнать, как вы подошли к этой проблеме.