36 votes

Se moquer de WebResponse à partir d'un WebRequest

J'ai enfin commencé à déconner avec la création de certaines applications qui fonctionnent avec web RESTful interfaces, cependant, je suis inquiet que je suis marteler leurs serveurs à chaque fois que j'appuyez sur la touche F5 pour exécuter une série de tests..

En gros, j'ai besoin d'obtenir une série de web les réponses, de sorte que je puisse le tester je suis d'analyse les réponses varient correctement, plutôt que de frapper leurs serveurs à chaque fois, je pensais que je pouvais le faire une fois, enregistrez le fichier XML et ensuite travailler localement.

Cependant, je ne vois pas comment je peux "se moquer de" un WebResponse, depuis (autant que je sache) ils ne peuvent être instanciées par WebRequest.GetResponse

Comment faites-vous pour aller sur se moquant de ce genre de chose? Faites-vous? J'ai juste n'aime vraiment pas le fait que je suis le martelage de leurs serveurs :S je ne veux pas changer le code de trop beaucoup, mais j'y attendre, il existe une manière élégante de le faire..

Mise À Jour Suite À L'Accepter

Aura la réponse a été la claque dans le visage, j'ai besoin, je savais qu'il me manquait un point fondamental!

  • Créer une Interface qui va retourner un objet proxy qui représente le XML.
  • Mettre en œuvre l'interface de deux fois, l'une qui utilise WebRequest, l'autre renvoie statique "réponses".
  • L'interface de mise en oeuvre puis instancie le type de retour en fonction de la réponse, ou le XML statiques.
  • Vous pouvez ensuite passer à la classe requise lors de l'essai ou à la production de la couche de service.

Une fois que j'ai le code frappé vers le haut, je vais coller des échantillons.

60voto

Richard Willis Points 806

J'ai trouvé cette question en cherchant à faire exactement la même chose. Ne pouvais pas trouver une réponse, n'importe où, mais après un peu plus de creuser constaté que l' .Net Framework a construit dans le soutien à ce projet.

Vous pouvez enregistrer un objet usine avec WebRequest.RegisterPrefix qui WebRequest.Create fera appel lors de l'utilisation du préfixe (ou url). L'usine de l'objet doit implémenter IWebRequestCreate qui est une méthode simple Create qui retourne un WebRequest. Ici vous pouvez retourner votre maquette WebRequest.

J'ai mis un exemple de code à http://blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/

14voto

escape-llc Points 755

Voici une solution qui ne nécessite pas de moqueries. Vous mettez en place tous les trois composantes de l' WebRequest: IWebRequestCreate WebRequest et WebResponse. Voir ci-dessous. Mon exemple génère faute de demandes (en jetant WebException), mais doit être capable de l'adapter à envoyer des "vraies" réponses:

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) {
    }
}

Vous devez créer un HttpWebResponse sous-classe, parce que vous ne pouvez pas en créer un.

La partie la plus délicate (en GetException() méthode) est l'alimentation dans les valeurs que vous ne peut pas ignorer, par exemple, StatusCode et c'est là que notre bestest copain SerializaionInfo vient en! C'est là que vous fournissez les valeurs ne peuvent pas remplacer. Évidemment, remplacer les pièces (de HttpWebResponse), vous pourrez, pour obtenir le reste du chemin.

Comment ai-je obtenir les "noms" dans tous ces AddValue() des appels? Depuis l'exception des messages! Il a été assez gentil pour me dire chacun à son tour, jusqu'à ce que je l'ai fait heureux.

Maintenant, le compilateur se plaint de "obsolète" mais présente cependant des œuvres, y compris .NET Framework version 4.

Voici une (de passage) de cas de test pour la référence:

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

2voto

Will Points 76760

Tu ne peux pas. La meilleure chose à faire est de l’envelopper dans un objet proxy, puis de s’en moquer. Sinon, vous devrez utiliser un framework fictif pouvant intercepter des types qui ne peuvent pas être fictifs, comme TypeMock. Mais vous parlez de dollars, là. Mieux vaut faire un peu d'emballage.


Apparemment, vous pouvez avec un peu de travail supplémentaire. Cochez la réponse la plus votée ici.

2voto

Simon Parsons Points 41

J'ai trouvé le blog suivant plus tôt, ce qui explique une approche plutôt sympa avec Microsoft Moles.

http://maraboustork.co.uk/index.php/2011/03/mocking-httpwebresponse-with-moles/

En bref, la solution suggère ce qui suit:

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

0voto

dr. evil Points 12196

Ce n'est pas une solution parfaite mais cela a déjà fonctionné pour moi et mérite une attention particulière pour la simplicité:

HTTPSimulator

Également un exemple de typemock documenté dans les forums de typemock :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X