webentwicklung-frage-antwort-db.com.de

Ist es möglich, eine .NET HttpWebResponse zu verspotten?

ich habe einen Integrationstest, der einige JSON-Ergebnisse von einem Drittanbieter-Server abruft. Es ist wirklich einfach und funktioniert großartig.

Ich hatte gehofft, nicht mehr auf diesen Server zuzugreifen und Moq (oder eine beliebige Mocking-Bibliothek wie ninject usw.) zu verwenden, um das Ergebnis zurückzuerzwingen.

ist das möglich?

Hier ist ein Beispielcode: -

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

und natürlich heißt diese methode aus meinem test.

Ich dachte, dass ich in diese Methode (oder eine Eigenschaft der Klasse?) Vielleicht ein verspottetes httpWebResponse oder etwas anderes übergehen muss, war mir aber nicht sicher, ob dies der Weg war. Außerdem ist die Antwort eine Ausgabe einer httpWebRequest.GetResponse()-Methode. Vielleicht muss ich nur ein verspottetes HttpWebRequest übergeben.

vorschläge mit einigem Beispielcode werden am meisten geschätzt!

62
Pure.Krome

Möglicherweise möchten Sie Ihren konsumierenden Code ändern, um eine Schnittstelle für eine Factory aufzunehmen, die Anforderungen und Antworten erstellt, die nachgestellt werden können und die die eigentliche Implementierung einschließen.

Update: Überarbeitung

Ich habe lange nach der Annahme meiner Antwort Ablehnungen erhalten, und ich gebe zu, dass meine ursprüngliche Antwort von schlechter Qualität war und eine große Annahme gemacht hat.

HttpWebRequest in 4.5+ verspotten

Die Verwirrung von meiner ursprünglichen Antwort liegt in der Tatsache, dass Sie HttpWebResponse in 4.5 verspotten können, aber nicht in früheren Versionen. Beim Verspotten in 4.5 werden auch veraltete Konstruktoren verwendet. Die empfohlene Vorgehensweise besteht darin, die Anforderung und die Antwort zu abstrahieren. Im Folgenden finden Sie einen vollständigen Funktionstest unter Verwendung von .NET 4.5 mit 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<HttpWebResponse>();
    response.Setup(c => c.GetResponseStream()).Returns(responseStream);

    var request = new Mock<HttpWebRequest>();
    request.Setup(c => c.GetResponse()).Returns(response.Object);

    var factory = new Mock<IHttpWebRequestFactory>();
    factory.Setup(c => c.Create(It.IsAny<string>()))
        .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);
}

Bessere Antwort: Zusammenfassung der Antwort und Anforderung

Hier ist eine absolut sichere Implementierung einer Abstraktion, die für frühere Versionen (mindestens bis 3.5) funktioniert:

[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<IHttpWebResponse>();
    response.Setup(c => c.GetResponseStream()).Returns(responseStream);

    var request = new Mock<IHttpWebRequest>();
    request.Setup(c => c.GetResponse()).Returns(response.Object);

    var factory = new Mock<IHttpWebRequestFactory>();
    factory.Setup(c => c.Create(It.IsAny<string>()))
        .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();
    }
}
67
HackedByChinese
8

Anstatt die HttpWebResponse zu verspotten, würde ich den Aufruf hinter einer Schnittstelle einschließen und diese Schnittstelle verspotten.

Wenn Sie testen, trifft die Webantwort auf die Site zu, die ich auch haben möchte. Dies ist ein anderer Test als wenn Klasse A die WebResponse-Schnittstelle aufruft, um die erforderlichen Daten abzurufen.

Zum Verspotten eines Interfaces bevorzuge ich Rhino-Verspottungen . Siehe hier zur Verwendung.

5
David Basarab

Wenn es hilft, finden Sie unten den Code, der in der akzeptierten Antwort mit NSubstitute anstelle von Moq dargestellt ist

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<HttpWebResponse>();
   response.GetResponseStream().Returns(responseStream);

   var request = Substitute.For<HttpWebRequest>();
   request.GetResponse().Returns(response);

   var factory = Substitute.For<IHttpWebRequestFactory>();
   factory.Create(Arg.Any<string>()).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);
}

Unit Test läuft und besteht erfolgreich.

Nach oben Abstimmung über die Antwort Ich habe schon seit einiger Zeit nach einer effektiven Vorgehensweise gesucht.

3
David Hall

Keiner der HTTP-Stacks von Microsoft wurde im Hinblick auf Komponententests und -trennung entwickelt.

Sie haben drei Möglichkeiten:

  • Machen Sie den Aufruf zum Web so klein wie möglich (d. H. Senden und holen Sie Daten zurück und übergeben Sie sie an andere Methoden) und testen Sie den Rest. Was den Webanruf betrifft, sollte dort viel Magie geschehen und sehr unkompliziert sein.
  • Binden Sie den HTTP-Aufruf in eine andere Klasse ein und übergeben Sie beim Testen Ihr Scheinobjekt.
  • Binden Sie HttpWebResponse und HttpWebRequest in zwei weitere Klassen ein. Dies hat das MVC-Team mit HttpContext getan.

Zweite Option:

interface IWebCaller
{
    string CallWeb(string address);
}
2
Aliostad

Sie können tatsächlich HttpWebResponse ohne Spott zurückgeben, siehe meine Antwort hier . Es werden keine "externen" Proxy-Schnittstellen benötigt, nur die "Standard" -Positionen WebRequestWebResponse und ICreateWebRequest.

Wenn Sie keinen Zugriff auf HttpWebResponse benötigen und nur mit WebResponse umgehen können, ist dies noch einfacher. Wir tun dies in unseren Unit-Tests, um "vorgefertigte" Inhaltsantworten für den Verbrauch zurückzugeben. Ich musste "die Extrameile gehen", um tatsächliche HTTP-Statuscodes zurückzugeben, um z.B. 404 Antworten, für die Sie HttpWebResponse verwenden müssen, damit Sie ua auf die Eigenschaft StatusCode zugreifen können.

Die anderen Lösungen, die davon ausgehen, dass alles HttpWebXXX ist, ignorieren alles, was von WebRequest.Create() unterstützt wird, mit Ausnahme von HTTP , das ein Handler für sein kann Jedes registrierte Präfix, das Sie verwenden möchten (über WebRequest.RegisterPrefix() und wenn Sie dies ignorieren, verpassen Sie es, da es eine großartige Möglichkeit ist, andere Inhaltsströme anzuzeigen, auf die Sie sonst keinen Zugriff haben, z. B. Embeeded Resource Streams, Dateistreams usw.

Das explizite Umwandeln der Rückgabe von WebRequest.Create() in HttpWebRequest ist ein Pfad zum Unterbrechen , da die Methode den Rückgabetyp zurückgibt ist WebRequest und zeigt wiederum einige Unkenntnisse darüber, wie diese API tatsächlich funktioniert.

1
escape-llc