Вопрос

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

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

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

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


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

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

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

HTTPSimulator

Также пример 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);
    }
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top