Поддельные WebResponse из WebRequest - PullRequest
33 голосов
/ 18 сентября 2008

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

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

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

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

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

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

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

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

Ответы [ 5 ]

59 голосов
/ 19 октября 2009

Я нашел этот вопрос, пытаясь сделать то же самое. Не смог найти нигде ответа, но после еще нескольких копаний обнаружил, что .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/

14 голосов
/ 04 июня 2012

Вот решение, которое не требует насмешек. Вы реализуете все три компонента 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;
        }
    }
3 голосов
/ 18 сентября 2008

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


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

2 голосов
/ 17 марта 2011

Ранее я обнаружил следующий блог, в котором объясняется довольно хороший подход с использованием 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.");
    }
0 голосов
/ 02 апреля 2009

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

HTTPSimulator

Также пример 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);
    }
  }
}
...