web-dev-qa-db-fra.com

Est-il possible de simuler un .NET HttpWebResponse?

j'ai un test d'intégration qui saisit le résultat json d'un serveur tiers. C'est vraiment simple et fonctionne très bien.

J'espérais arrêter de frapper réellement ce serveur et d'utiliser Moq (ou n'importe quelle bibliothèque Mocking, comme ninject, etc.) pour détourner et forcer le résultat de retour.

est-ce possible?

Voici un exemple de code: -

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

et bien sûr, cette méthode est appelée à partir de mon test.

Je pensais que je devais peut-être passer dans cette méthode (ou une propriété de la classe?) Un httpWebResponse moqué ou quelque chose mais je ne savais pas trop si c'était le cas. De plus, la réponse est une sortie d'une méthode httpWebRequest.GetResponse() .. alors peut-être ai-je juste besoin de passer un HttpWebRequest?.

toutes les suggestions avec un exemple de code seraient les plus appréciées!

62
Pure.Krome

Vous souhaiterez peut-être modifier votre code consommateur pour intégrer une interface pour une fabrique qui crée des demandes et des réponses qui peuvent être moquées et qui enveloppent l'implémentation réelle.

Mise à jour: revisiter

J'ai reçu des votes négatifs longtemps après que ma réponse a été acceptée, et j'admets que ma réponse d'origine était de mauvaise qualité et a fait une grande hypothèse.

Se moquer de HttpWebRequest en 4.5+

La confusion de ma réponse d'origine réside dans le fait que vous pouvez vous moquer de HttpWebResponse dans 4.5, mais pas dans les versions antérieures. Se moquer de lui en 4.5 utilise également des constructeurs obsolètes. Ainsi, la ligne de conduite recommandée consiste à résumer la demande et la réponse. Quoi qu'il en soit, ci-dessous est un test de travail complet utilisant .NET 4.5 avec 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);
}

Meilleure réponse: résumez la réponse et la demande

Voici une implémentation bare-bones plus sûre d'une abstraction qui fonctionnera pour les versions précédentes (enfin, jusqu'à 3,5 au moins):

[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
Mauricio Scheffer

Plutôt que de se moquer de HttpWebResponse, je préfère envelopper l'appel derrière une interface et me moquer de cette interface.

Si vous testez la réponse Web arrive-t-elle sur le site, je le veux aussi, c'est un test différent de si la classe A appelle l'interface WebResponse pour obtenir les données nécessaires.

Pour se moquer d'une interface, je préfère Rhino se moque . Voir ici sur la façon de l'utiliser.

5
David Basarab

Si cela peut aider, veuillez trouver ci-dessous le code illustré dans la réponse acceptée en utilisant NSubstitute à la place 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<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);
}

Le test unitaire s'est exécuté et a réussi.

Vote positif sur la réponse Je cherche depuis un certain temps comment faire cela efficacement.

3
David Hall

Aucune des piles HTTP de Microsoft n'a été développée en gardant à l'esprit les tests unitaires et la séparation.

Vous avez trois options:

  • Faites l'appel au Web le plus petit possible (c'est-à-dire envoyez et récupérez des données et passez à d'autres méthodes) et testez le reste. En ce qui concerne l'appel Web, il devrait y avoir beaucoup de magie et très simple.
  • Enveloppez l'appel HTTP dans une autre classe et passez votre objet simulé pendant le test.
  • Enveloppez HttpWebResponse et HttpWebRequest par deux autres classes. C'est ce que l'équipe MVC a fait avec HttpContext.

Deuxième option:

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

Vous pouvez réellement retourner HttpWebResponse sans vous moquer, voir ma réponse ici . Il ne nécessite aucune interface de proxy "externe", uniquement les interfaces "standard" WebRequestWebResponse et ICreateWebRequest.

Si vous n'avez pas besoin d'accéder à HttpWebResponse et ne pouvez gérer que WebResponse, c'est encore plus facile; nous le faisons dans nos tests unitaires pour renvoyer des réponses de contenu "préfabriquées" à la consommation. J'ai dû "faire un effort supplémentaire" pour renvoyer les codes d'état HTTP réels, pour simuler par exemple 404 réponses qui nécessitent que vous utilisiez HttpWebResponse pour pouvoir accéder à la propriété StatusCode et al.

Les autres solutions supposant que tout est HttpWebXXX ignorent tout ce qui est pris en charge par WebRequest.Create() sauf HTTP , qui peut être un gestionnaire pour tout préfixe enregistré que vous souhaitez utiliser (via WebRequest.RegisterPrefix() et si vous l'ignorez, vous le manquez, car c'est un excellent moyen d'exposer d'autres flux de contenu auxquels vous n'avez autrement aucun moyen d'accéder, par exemple Embeeded Resource Flux, flux de fichiers, etc.

En outre, le transtypage explicite du retour de WebRequest.Create() en HttpWebRequest est un chemin vers la rupture , puisque la méthode retourne le type est WebRequest et encore une fois, montre une certaine ignorance du fonctionnement réel de cette API.

1
escape-llc