Мокинг WebResponse из WebRequest

Я наконец-то начал возиться с созданием некоторых приложений, которые работают с веб-интерфейсами RESTful, однако меня беспокоит, что я забиваю их серверы каждый раз, когда нажимаю F5, чтобы запустить серию тестов.

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

Однако я не понимаю, как я могу "издеваться" над WebResponse, поскольку (AFAIK) они могут быть созданы только с помощью WebRequest.GetResponse

Как вы, ребята, издеваетесь над такими вещами? Ты? Мне просто не нравится, что я забиваю их серверы: S Я не хочу сильно менять код тоже, но я думаю, что есть элегантный способ сделать это ...

Обновить после принятия

Ответом Уилла была пощечина, в которой я нуждался, я знал, что упускаю главное!

  • Создайте интерфейс, который вернет прокси-объект, представляющий XML.
  • Реализуйте интерфейс дважды: один использует WebRequest, а другой возвращает статические «ответы».
  • Внедрение интерфейса затем создает экземпляр возвращаемого типа на основе ответа или статического XML.
  • Затем вы можете передать требуемый класс при тестировании или производстве на уровень сервиса.

Как только код будет готов, я вставлю несколько примеров.

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

Nick 30.07.2009 11:12

Привет, Ник, как в обновлении. В итоге я начал программировать с использованием интерфейса. Затем я реализую интерфейс дважды. Тот, который возвращает статический контент, тот, который фактически использует WebRequest. Затем вы можете UT потребителя, а затем просто использовать фактический сейф, зная, что вы протестировали класс потребителя. Всегда помните: «не тестируйте чужой код» (например, WebRequest).

Rob Cooper 31.07.2009 14:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
35
2
14 856
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы не можете. Лучше всего обернуть его в прокси-объект, а затем поиздеваться над ним. В качестве альтернативы вам придется использовать фиктивный фреймворк, который может перехватывать типы, которые нельзя имитировать, например TypeMock. Но вы говорите о долларах. Лучше сделать небольшое обертывание.


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

Не могли бы вы пояснить, как я могу "обернуть", не затрагивая основной код? Как только этот GetResponse будет вызван, бум! он работает ... Очевидно, мне нужно сделать некоторую инъекцию зависимости, но я не понимаю, как я могу добраться до нужного мне объекта, понимаете?

Rob Cooper 18.09.2008 00:40

Не сомневайся, будучи идиотом. Я с тобой. Создайте прокси-объект для передачи результата, интерфейс для вызовов. Реализовать в двух классах: 1 = WebRequest, 2 = Статические объекты). Теперь это имеет смысл, спасибо, что ударил меня по лицу и заставил меня увидеть смысл, я знал, что не могу видеть лес сквозь деревья :)

Rob Cooper 18.09.2008 00:48

это неверно, поскольку ответ Ричарда Уиллиса ниже показывает, как это можно сделать. WebResponse МОЖЕТ быть смоделирован с помощью кода, на который он ссылается.

tmont 03.04.2010 02:22

@tmont интересно и +1 Уиллису. Отредактировано для ясности. Хорошо, что они это хорошо спрятали ...

user1228 03.04.2010 20:43

Это не идеальное решение, но оно работало у меня раньше и заслуживает особой заботы о простоте:

HTTPSимулятор

Также пример 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 для просмотра кодов состояния ... тогда вам нужно увидеть мой ответ ниже!

escape-llc 13.06.2012 19:52

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

Nick 19.06.2013 20:49

Ранее я нашел следующий блог, в котором объясняется довольно хороший подход с использованием 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, потому что «устаревший» конструктор был удален.

desautelsj 28.12.2016 05:15

Другие вопросы по теме