Pergunta

Eu finalmente começou a mexer com a criação de alguns aplicativos que trabalham com interfaces web RESTful, no entanto, eu estou preocupado que eu estou martelando seus servidores cada vez que eu bater F5 para executar uma série de testes ..

Basicamente, eu preciso para obter uma série de respostas web para que eu possa testar Eu estou analisando as respostas variando corretamente, em vez de bater os seus servidores de cada vez, eu pensei que eu poderia fazer isso uma vez, salvar o XML e, em seguida, trabalhar localmente.

No entanto, não vejo como posso "simulado" um WebResponse, uma vez que (AFAIK) que só pode ser instanciado por WebRequest.GetResponse

Como vocês vão cerca zombando esse tipo de coisa? Você? Eu realmente não gosto do fato de que estou martelando seus servidores: S Eu não quero alterar o código de muito muito, mas espero que há uma maneira elegante de fazer isso ..

Atualização Após Aceitar

A resposta de Will foi o tapa na cara que eu precisava, eu sabia que estava faltando um ponto fundamental!

  • Criar uma interface que irá retornar um objeto de proxy que representa o XML.
  • Implementar a interface duas vezes, uma que usa WebRequest, o outro que retorna estáticos "respostas".
  • O implmentation interface de então ou instancia o tipo de retorno com base na resposta, ou o XML estático.
  • Você pode então passar a classe para o ensaio ou a produção para a camada de serviço.

Assim que eu tiver o código batido acima, eu vou colar algumas amostras.

Foi útil?

Solução

Eu encontrei esta pergunta ao olhar para fazer exatamente a mesma coisa. Não foi possível encontrar um lugar resposta, mas depois de um pouco mais de escavação descobriu que o .NET Framework tem suporte embutido para isso.

Você pode registrar um objeto de fábrica com WebRequest.RegisterPrefix que WebRequest.Create vai chamar ao usar esse prefixo (ou URL). O objeto de fábrica deve implementar IWebRequestCreate que tem um único Create método que retorna um WebRequest. Aqui você pode devolver o seu WebRequest simulada.

Eu coloquei um código de exemplo acima em http: // blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/

Outras dicas

Aqui está uma solução que não requer zombando. Você implementar todos os três componentes do WebRequest: IWebRequestCreate WebRequest e WebResponse. Ver abaixo. Meu exemplo gera falhando pedidos (por atirar WebException), mas deve ser capaz de adaptá-lo para enviar respostas "reais":

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

Você deve criar uma subclasse HttpWebResponse, porque não é possível de outra forma criar um.

A parte complicada (no método GetException()) está alimentando nos valores que não pode substituir, por exemplo, StatusCode e é aí que o nosso bestest SerializaionInfo amigo vem em! Isto é onde você fornecer os valores que você não pode substituir. Obviamente, substituir as peças (de HttpWebResponse) que são capazes, para obter o resto do caminho até lá.

Como é que eu obter os "nomes" em todas aquelas chamadas AddValue()? A partir das mensagens de exceção! Foi bom o suficiente para me dizer a cada um por sua vez, até que eu fiz-lo feliz.

Agora, o compilador irá reclamar "obsoleto", mas isso, no entanto, funciona, incluindo .NET Framework versão 4.

Aqui está uma (passando) caso de teste para referência:

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

Você não pode. Melhor coisa a fazer é envolvê-la em um objeto proxy, e depois zombar isso. Alternativamente, você teria que usar um quadro de simulação que tipos pode interceptar que não pode ser ridicularizado, como TypeMock. Mas você está falando sobre dinheiro, não. Melhor para fazer um pouco de embrulho.


Aparentemente você pode com um pouco de trabalho extra. Verifique a maior resposta votado aqui.

Eu encontrei o seguinte blogue anterior, que explica uma abordagem bastante agradável usando Microsoft Moles.

http://maraboustork.co.uk /index.php/2011/03/mocking-httpwebresponse-with-moles/

Em suma, a solução sugere a seguinte:

    [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.");
    }

Esta não é uma solução perfeita, no entanto, funcionou para mim antes e merece um cuidado extra para a simplicidade:

HTTPSimulator

Além disso, um exemplo typemock documentado em fóruns 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);
    }
  }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top