É possível ridicularizar um .NET HttpWebResponse?

Eu tenho um teste de integração que pega um resultado json de um servidor de terceiros. É muito simples e funciona muito bem.

Eu estava esperando parar realmente de acertar este servidor e usar o Moq (ou qualquer biblioteca Mocking, como ninject, etc) para seqüestrar e forçar o resultado de retorno.

Isso é possível?

Aqui está um código de amostra:

 public Foo GoGetSomeJsonForMePleaseKThxBai() { // prep stuff ... // Now get json please. HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("Http://some.fancypants.site/api/hiThere); httpWebRequest.Method = WebRequestMethods.Http.Get; string responseText; using (var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { json = streamReader.ReadToEnd().ToLowerInvariant(); } } // Check the value of the json... etc.. } 

e, claro, esse método é chamado do meu teste.

Eu estava pensando que talvez eu precise passar para este método (ou uma propriedade da class?) Um httpWebResponse ridicularizado ou algo assim, mas não tinha certeza se esse era o caminho. Além disso, a resposta é uma saída de um método httpWebRequest.GetResponse() .. então talvez eu só precise passar em um HttpWebRequest .

qualquer sugestão com algum código de exemplo seria mais apreciada!

Você pode desejar alterar seu código consumidor para obter uma interface para uma fábrica que cria solicitações e respostas que podem ser ridicularizadas e que envolvem a implementação real.

Atualização: Revisitando

Há muito tempo recebo votos negativos depois que minha resposta foi aceita, e admito que minha resposta original era de má qualidade e fiz uma grande suposição.

Zombando HttpWebRequest em 4.5+

A confusão da minha resposta original reside no fato de que você pode simular o HttpWebResponse na versão 4.5, mas não em versões anteriores. Mocking em 4.5 também utiliza construtores obsoletos. Portanto, o curso de ação recomendado é abstrair a solicitação e a resposta. De qualquer forma, abaixo está um teste completo usando o .NET 4.5 com o Moq 4.2.

 [Test] public void Create_should_create_request_and_respond_with_stream() { // arrange var expected = "response content"; var expectedBytes = Encoding.UTF8.GetBytes(expected); var responseStream = new MemoryStream(); responseStream.Write(expectedBytes, 0, expectedBytes.Length); responseStream.Seek(0, SeekOrigin.Begin); var response = new Mock(); response.Setup(c => c.GetResponseStream()).Returns(responseStream); var request = new Mock(); request.Setup(c => c.GetResponse()).Returns(response.Object); var factory = new Mock(); factory.Setup(c => c.Create(It.IsAny())) .Returns(request.Object); // act var actualRequest = factory.Object.Create("http://www.google.com"); actualRequest.Method = WebRequestMethods.Http.Get; string actual; using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { actual = streamReader.ReadToEnd(); } } // assert actual.Should().Be(expected); } public interface IHttpWebRequestFactory { HttpWebRequest Create(string uri); } 

Melhor resposta: Resumo da Resposta e Solicitação

Aqui está uma implementação mais simples de uma abstração que funcionará para versões anteriores (bem, para pelo menos 3.5):

 [Test] public void Create_should_create_request_and_respond_with_stream() { // arrange var expected = "response content"; var expectedBytes = Encoding.UTF8.GetBytes(expected); var responseStream = new MemoryStream(); responseStream.Write(expectedBytes, 0, expectedBytes.Length); responseStream.Seek(0, SeekOrigin.Begin); var response = new Mock(); response.Setup(c => c.GetResponseStream()).Returns(responseStream); var request = new Mock(); request.Setup(c => c.GetResponse()).Returns(response.Object); var factory = new Mock(); factory.Setup(c => c.Create(It.IsAny())) .Returns(request.Object); // act var actualRequest = factory.Object.Create("http://www.google.com"); actualRequest.Method = WebRequestMethods.Http.Get; string actual; using (var httpWebResponse = actualRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { actual = streamReader.ReadToEnd(); } } // assert actual.Should().Be(expected); } public interface IHttpWebRequest { // expose the members you need string Method { get; set; } IHttpWebResponse GetResponse(); } public interface IHttpWebResponse : IDisposable { // expose the members you need Stream GetResponseStream(); } public interface IHttpWebRequestFactory { IHttpWebRequest Create(string uri); } // barebones implementation private class HttpWebRequestFactory : IHttpWebRequestFactory { public IHttpWebRequest Create(string uri) { return new WrapHttpWebRequest((HttpWebRequest)WebRequest.Create(uri)); } } public class WrapHttpWebRequest : IHttpWebRequest { private readonly HttpWebRequest _request; public WrapHttpWebRequest(HttpWebRequest request) { _request = request; } public string Method { get { return _request.Method; } set { _request.Method = value; } } public IHttpWebResponse GetResponse() { return new WrapHttpWebResponse((HttpWebResponse)_request.GetResponse()); } } public class WrapHttpWebResponse : IHttpWebResponse { private WebResponse _response; public WrapHttpWebResponse(HttpWebResponse response) { _response = response; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { if (_response != null) { ((IDisposable)_response).Dispose(); _response = null; } } } public Stream GetResponseStream() { return _response.GetResponseStream(); } } 

Eu escrevi um conjunto de interfaces e adaptadores precisamente para esse propósito há alguns anos atrás.

Em vez de zombar do HttpWebResponse, eu envolvia a chamada por trás de uma interface e zombava dessa interface.

Se você está testando, a resposta da web atingiu o site que eu quero também, que é um teste diferente do que se a class A chamar a interface WebResponse para obter os dados necessários.

Por zombar de uma interface eu prefiro zombarias Rhino . Veja aqui como usá-lo.

Se ajudar, por favor, encontrar abaixo o código ilustrado na resposta aceita usando NSubstitute no lugar de Moq

 using NSubstitute; /*+ other assemblies*/ [TestMethod] public void Create_should_create_request_and_respond_with_stream() { //Arrange var expected = "response content"; var expectedBytes = Encoding.UTF8.GetBytes(expected); var responseStream = new MemoryStream(); responseStream.Write(expectedBytes, 0, expectedBytes.Length); responseStream.Seek(0, SeekOrigin.Begin); var response = Substitute.For(); response.GetResponseStream().Returns(responseStream); var request = Substitute.For(); request.GetResponse().Returns(response); var factory = Substitute.For(); factory.Create(Arg.Any()).Returns(request); //Act var actualRequest = factory.Create("http://www.google.com"); actualRequest.Method = WebRequestMethods.Http.Get; string actual; using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { actual = streamReader.ReadToEnd(); } } //Assert Assert.AreEqual(expected, actual); } public interface IHttpWebRequestFactory { HttpWebRequest Create(string uri); } 

Teste de unidade executado e passa com sucesso.

Até voto dado na resposta Eu tenho procurado por algum tempo como fazer isso de forma eficaz.

Nenhuma das pilha de HTTP da Microsoft foi desenvolvida com o teste de unidade e a separação em mente.

Você tem três opções:

  • Faça a chamada para a web o menor possível (ou seja, envie e recupere dados e passe para outros methods) e teste o resto. No que diz respeito à chamada da web, deve haver muita mágica acontecendo e muito direta.
  • Envolva a chamada HTTP em outra class e passe seu object falso durante o teste.
  • Envolva HttpWebResponse e HttpWebRequest por duas outras classs. Isso é o que a equipe do MVC fez com o HttpContext .

Segunda opçao:

 interface IWebCaller { string CallWeb(string address); } 

Você pode realmente retornar HttpWebResponse sem zombar, veja minha resposta aqui . Ele não requer nenhuma interface de proxy “externa”, apenas o WebRequest “padrão” e o ICreateWebRequest .

Se você não precisa acessar o HttpWebResponse e pode lidar com apenas o WebResponse , é ainda mais fácil; Fazemos isso em nossos testes unitários para retornar respostas de conteúdo “pré-fabricadas” para consumo. Eu tive que “ir a milha extra”, a fim de retornar códigos de status HTTP reais, para simular, por exemplo, respostas 404 que requer que você use HttpWebResponse para que você possa acessar a propriedade StatusCode et al.

As outras soluções assumindo que tudo é HttpWebXXX ignora tudo o que é suportado pelo WebRequest.Create() exceto HTTP , que pode ser um manipulador para qualquer prefixo registrado que você queira usar (via WebRequest.RegisterPrefix() e se você está ignorando isso, está perdendo , porque é uma ótima maneira de expor outros streams de conteúdo que você não tem como acessar, por exemplo, Fluxos de Recursos Embedidos, Fluxos de Arquivos, etc.

Além disso, lançar explicitamente o retorno de WebRequest.Create() para HttpWebRequest é um caminho para a quebra , uma vez que o tipo de retorno do método é WebRequest e, novamente, mostra alguma ignorância de como essa API realmente funciona.

Eu encontrei uma ótima solução neste post :

É muito fácil de usar, você só precisa fazer isso:

 string response = "my response string here"; WebRequest.RegisterPrefix("test", new TestWebRequestCreate()); TestWebRequest request = TestWebRequestCreate.CreateTestRequest(response); 

E copie esses arquivos para o seu projeto:

  class TestWebRequestCreate : IWebRequestCreate { static WebRequest nextRequest; static object lockObject = new object(); static public WebRequest NextRequest { get { return nextRequest ;} set { lock (lockObject) { nextRequest = value; } } } /// See . public WebRequest Create(Uri uri) { return nextRequest; } /// Utility method for creating a TestWebRequest and setting /// it to be the next WebRequest to use. /// The response the TestWebRequest will return. public static TestWebRequest CreateTestRequest(string response) { TestWebRequest request = new TestWebRequest(response); NextRequest = request; return request; } } class TestWebRequest : WebRequest { MemoryStream requestStream = new MemoryStream(); MemoryStream responseStream; public override string Method { get; set; } public override string ContentType { get; set; } public override long ContentLength { get; set; } /// Initializes a new instance of  /// with the response to return. public TestWebRequest(string response) { responseStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(response)); } /// Returns the request contents as a string. public string ContentAsString() { return System.Text.Encoding.UTF8.GetString(requestStream.ToArray()); } /// See . public override Stream GetRequestStream() { return requestStream; } /// See . public override WebResponse GetResponse() { return new TestWebReponse(responseStream); } } class TestWebReponse : WebResponse { Stream responseStream; /// Initializes a new instance of  /// with the response stream to return. public TestWebReponse(Stream responseStream) { this.responseStream = responseStream; } /// See . public override Stream GetResponseStream() { return responseStream; } }