¿Es posible burlarse de .NET HttpWebResponse?

Tengo una prueba de integración que toma un resultado json de un servidor de terceros. Es realmente simple y funciona genial.

Esperaba dejar de golpear este servidor y usar Moq (o cualquier biblioteca de Mocking, como ninject, etc.) para secuestrar y forzar el resultado de la devolución.

¿es posible?

Aquí hay un código de muestra:

 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.. } 

y por supuesto, este método se llama desde mi prueba.

Estaba pensando que tal vez tenga que pasar a este método (¿o una propiedad de la clase?) A un httpWebResponse burlado o algo así, pero no estaba muy seguro de si este era el camino. Además, la respuesta es un resultado de un método httpWebRequest.GetResponse() … así que tal vez solo necesito pasar HttpWebRequest ?

cualquier sugerencia con código de muestra sería muy apreciada.

Es posible que desee cambiar su código de consumo para incluir una interfaz para una fábrica que crea solicitudes y respuestas que pueden ser burladas y que envuelven la implementación real.

Actualización: Revisitando

He estado recibiendo votos bajos mucho después de que mi respuesta fue aceptada, y admito que mi respuesta original fue de mala calidad y asumí una gran suposición.

Burlarse de HttpWebRequest en 4.5+

La confusión de mi respuesta original radica en el hecho de que puede burlarse de HttpWebResponse en 4.5, pero no en versiones anteriores. Burlarse de él en 4.5 también utiliza constructores obsoletos. Por lo tanto, el curso de acción recomendado es resumir la solicitud y la respuesta. De todos modos, a continuación se muestra una prueba de trabajo completa que utiliza .NET 4.5 con 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); } 

Mejor respuesta: Resum la respuesta y la solicitud

Aquí hay una implementación más segura de una abstracción que funcionará para versiones anteriores (bueno, hasta 3.5 al menos):

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

Escribí un conjunto de interfaces y adaptadores precisamente para este propósito hace algunos años.

En lugar de burlarse de HttpWebResponse es que cerraría la llamada detrás de una interfaz y se burlaría de esa interfaz.

Si está realizando pruebas, la respuesta web golpea el sitio que yo quiero, esa es una prueba diferente a la que hace la clase A si llama a la interfaz WebResponse para obtener los datos necesarios.

Para burlarse de una interfaz, prefiero que Rhino se burle . Vea aquí sobre cómo usarlo.

Si es útil, encuentre a continuación el código ilustrado en la respuesta aceptada utilizando NSubstitute en 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); } 

La prueba de unidad se ejecuta y pasa con éxito.

Voto dado sobre la respuesta He estado buscando algún tiempo cómo hacer esto de manera efectiva.

Ninguna de las stacks HTTP de Microsoft se desarrolló teniendo en cuenta las pruebas de unidades y la separación.

Tienes tres opciones:

  • Haga la llamada a la web lo más pequeña posible (es decir, envíe y recupere datos y pase a otros métodos) y pruebe el rest. En lo que se refiere a la llamada web, debe haber mucha magia sucediendo allí y muy sencillo.
  • Envuelva la llamada HTTP en otra clase y pase su objeto simulado mientras prueba.
  • Envuelva HttpWebResponse y HttpWebRequest por otras dos clases. Esto es lo que hizo el equipo de MVC con HttpContext .

Segunda opción:

 interface IWebCaller { string CallWeb(string address); } 

En realidad puede devolver HttpWebResponse sin burlarse, vea mi respuesta aquí . No requiere interfaces de WebRequest “externas”, solo WebRequest WebResponse “estándar” e ICreateWebRequest .

Si no necesita acceder a HttpWebResponse y puede manejar solo WebResponse , es aún más fácil; lo hacemos en nuestras pruebas unitarias para devolver respuestas de contenido “prefabricadas” para el consumo. Tuve que “hacer un esfuerzo adicional” para devolver los códigos de estado HTTP reales, para simular, por ejemplo, respuestas 404 que requieren el uso de HttpWebResponse para que pueda acceder a la propiedad StatusCode y otros.

Las otras soluciones suponiendo que todo es HttpWebXXX ignora todo lo soportado por WebRequest.Create() excepto HTTP , que puede ser un controlador para cualquier prefijo registrado que le interese usar (a través de WebRequest.RegisterPrefix() y si está ignorando eso, se está perdiendo , porque es una gran manera de exponer otras secuencias de contenido a las que de otra manera no tendremos acceso, por ejemplo, flujos de recursos embebidos, secuencias de archivos, etc.

Además, emitir explícitamente el retorno de WebRequest.Create() a HttpWebRequest es una ruta de acceso a la rotura , ya que el tipo de retorno del método es WebRequest y, nuevamente, muestra cierta ignorancia sobre cómo funciona realmente la API.

Encontré una gran solución en esta publicación de blog :

Es muy fácil de usar, solo necesitas hacer esto:

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

Y copie esos archivos a su proyecto:

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