Подтвердить что ты не робот

Отказывание WebResponse от WebRequest

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

Я нашел этот вопрос, пытаясь сделать то же самое. Не удалось найти ответ нигде, но после того, как немного больше было найдено, что .NET Framework имеет поддержку для этого.

Вы можете зарегистрировать объект factory с помощью WebRequest.RegisterPrefix, который WebRequest.Create будет вызывать при использовании этого префикса (или url). Объект factory должен реализовать IWebRequestCreate, который имеет единственный метод Create, который возвращает WebRequest. Здесь вы можете вернуть свой макет WebRequest.

Я привел пример кода http://blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/

Ответ 2

Вот решение, которое не требует насмешек. Вы реализуете все три компонента 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

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


По-видимому, вы можете с небольшой дополнительной работой. Проверьте наивысший проголосовавший ответ.

Ответ 4

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

Ответ 5

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

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);
    }
  }
}