Question

J'ai finalement commencé à m'amuser à créer des applications fonctionnant avec des interfaces Web RESTful. Cependant, je crains que je ne sers de marteler leurs serveurs chaque fois que j'appuie sur F5 pour exécuter une série de tests.

En gros, je dois obtenir une série de réponses Web pour pouvoir analyser correctement les différentes réponses plutôt que de toucher leurs serveurs à chaque fois. Je pensais pouvoir le faire une fois, enregistrer le fichier XML puis travailler localement.

Cependant, je ne vois pas comment je peux "me moquer" de WebResponse, car (autant que je sache), ils ne peuvent être instanciés que par WebRequest.GetResponse

Comment vous moquez-vous de ce genre de chose? Le faites vous? Je n'aime vraiment pas vraiment le fait que je martèle leurs serveurs: S Je ne veux pas trop changer le code aussi , mais je m'attends à ce qu'il y ait une manière élégante de le faire ..

Mise à jour après acceptation

La réponse de Will était la gifle dont j'avais besoin, je savais qu'il me manquait un point fondamental!

  • Créez une interface qui renverra un objet proxy représentant le code XML.
  • Implémentez l'interface à deux reprises, l'une utilisant WebRequest, l'autre renvoyant des "réponses" statiques.
  • L'implémentation d'interface instancie alors le type de retour en fonction de la réponse ou le XML statique.
  • Vous pouvez ensuite transmettre la classe requise lors des tests ou en production à la couche service.

Une fois le code saisi, je vais coller des exemples.

Était-ce utile?

La solution

J'ai trouvé cette question en cherchant à faire exactement la même chose. Nous n'avons trouvé aucune réponse, mais après un peu plus de fouilles, nous avons constaté que .Net Framework prenait en charge cette fonctionnalité.

Vous pouvez enregistrer un objet d'usine avec WebRequest.RegisterPrefix que WebRequest.Create appellera lors de l'utilisation de ce préfixe (ou url). L'objet de fabrique doit implémenter IWebRequestCreate qui possède une méthode unique Create qui renvoie un WebRequest . Ici, vous pouvez retourner votre modèle WebRequest .

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

Autres conseils

Voici une solution qui ne nécessite pas de moqueries. Vous implémentez les trois composants de WebRequest : IWebRequestCreate WebRequest et WebResponse . Voir ci-dessous. Mon exemple génère des requêtes en échec (en lançant WebException ), mais devrait pouvoir l'adapter pour envoyer un message "réel". 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 une sous-classe HttpWebResponse , car vous ne pourriez pas en créer une autre.

La partie délicate (dans la méthode GetException () ) introduit les valeurs que vous ne pouvez pas remplacer, par exemple. StatusCode et c’est là que notre meilleur ami SerializaionInfo entre en jeu! C'est ici que vous fournissez les valeurs que vous ne pouvez pas remplacer. Évidemment, remplacez les parties (de HttpWebResponse ) que vous pouvez obtenir pour obtenir le reste du chemin.

Comment ai-je obtenu les "noms"? dans tous ces appels AddValue () ? Des messages d'exception! J'ai eu la gentillesse de me les raconter à tour de rôle, jusqu'à ce que je le rende heureux.

À présent, le compilateur se plaint de "obsolète". mais cela fonctionne néanmoins, y compris .NET Framework version 4.

Voici un scénario de test (réussi) pour 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;
        }
    }

Vous ne pouvez 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 le faire avec un peu de travail supplémentaire. Cochez la réponse la plus votée ici.

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

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

Voir aussi un exemple de code mémoire 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);
    }
  }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top