xml - consumir - httpwebrequest c# example
Mocking WebResponse de una WebRequest (5)
Aquí hay una solución que no requiere burla. Implementa los tres componentes de WebRequest
: IWebRequestCreate
WebRequest
y WebResponse
. Vea abajo. Mi ejemplo genera solicitudes WebException
(lanzando WebException
), pero debería ser capaz de adaptarlo para enviar respuestas "reales":
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) {
}
}
Debe crear una subclase HttpWebResponse
, porque de lo contrario no puede crear una.
La parte GetException()
método GetException()
) está GetException()
los valores que no puede anular, por ejemplo, StatusCode
y ¡aquí es donde entra nuestro mejor amigo, SerializaionInfo
! Aquí es donde proporciona los valores que no puede anular. Obviamente, anula las partes (de HttpWebResponse
) que puedas, para obtener el resto del camino hasta allí.
¿Cómo obtuve los "nombres" en todas esas llamadas AddValue()
? De los mensajes de excepción! Fue lo suficientemente agradable para decirme a cada uno por turno, hasta que lo hice feliz.
Ahora, el compilador se quejará de "obsoleto", pero esto funciona, incluso .NET Framework versión 4.
Aquí hay un caso de prueba (pasajero) para referencia:
[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;
}
}
Finalmente, he empezado a jugar con la creación de algunas aplicaciones que funcionan con interfaces web RESTful, sin embargo, me preocupa que estoy martillando sus servidores cada vez que presiono F5 para ejecutar una serie de pruebas.
Básicamente, necesito obtener una serie de respuestas web para poder probar. Estoy analizando las distintas respuestas de manera correcta, en lugar de presionar sus servidores cada vez, pensé que podría hacer esto una vez, guardar el XML y luego trabajar localmente.
Sin embargo, no veo cómo puedo "burlarme" de una respuesta web, ya que (AFAIK) solo pueden ser instanciadas por WebRequest.GetResponse
¿Cómo van a burlarse de este tipo de cosas? ¿Vos si? Realmente no me gusta el hecho de que estoy martillando sus servidores: SI no quiero cambiar demasiado el código, pero espero que haya una forma elegante de hacerlo ...
Actualización siguiente Aceptar
La respuesta de Will fue la bofetada en la cara que necesitaba, ¡sabía que me faltaba un punto fundamental!
- Cree una interfaz que devolverá un objeto proxy que representa el XML.
- Implemente la interfaz dos veces, una que use WebRequest, la otra que devuelva "respuestas" estáticas.
- La implementación de la interfaz luego instancia el tipo de devolución en función de la respuesta o el XML estático.
- Luego puede pasar la clase requerida al probar o en la producción a la capa de servicio.
Una vez que tenga el código estropeado, pegaré algunas muestras.
Encontré el siguiente blog antes, que explica un enfoque bastante agradable con Microsoft Moles.
http://maraboustork.co.uk/index.php/2011/03/mocking-httpwebresponse-with-moles/
En resumen, la solución sugiere lo siguiente:
[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.");
}
Encontré esta pregunta mientras buscaba hacer exactamente lo mismo. No se pudo encontrar una respuesta en ninguna parte, pero después de un poco más de búsqueda descubrí que .Net Framework ha incorporado soporte para esto.
Puede registrar un objeto de fábrica con WebRequest.RegisterPrefix
que WebRequest.Create
llamará cuando use ese prefijo (o url). El objeto de fábrica debe implementar IWebRequestCreate
que tiene un único método Create
que devuelve una WebRequest
. Aquí puede devolver su WebRequest
simulada.
He puesto un código de muestra en http://blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/
Esta no es una solución perfecta, sin embargo, funcionó para mí antes y merece un cuidado extra por la simplicidad:
También un ejemplo tipográfico documentado en foros de tipografía :
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);
}
}
}
No puedes. Lo mejor es envolverlo en un objeto proxy y luego simularlo. Alternativamente, tendrías que utilizar un marco simulado que pueda interceptar tipos que no se pueden burlar, como TypeMock. Pero estás hablando de dinero, allí. Mejor hacer un poco de envoltura.
Aparentemente puedes con un poco de trabajo extra. Verifique la respuesta más votada aquí.