【问题标题】:Mocking WebResponse's from a WebRequest从 WebRequest 模拟 WebResponse
【发布时间】:2010-09-10 08:58:48
【问题描述】:

我终于开始着手创建一些与 RESTful Web 界面配合使用的应用程序,但是,我担心每次按 F5 运行一系列测试时,我都会对他们的服务器造成冲击。

基本上,我需要获取一系列 Web 响应,以便测试我是否正确解析了不同的响应,而不是每次都访问他们的服务器,我想我可以这样做一次,保存 XML,然后在本地工作。

但是,我看不出如何“模拟”WebResponse,因为(AFAIK)它们只能由 WebRequest.GetResponse

实例化

你们如何嘲笑这种事情?你?我真的不喜欢我正在锤击他们的服务器这一事实:S 我不想更改代码太多,但我希望有一种优雅的方式来做到这一点..

接受后更新

威尔的回答是我需要的一记耳光,我知道我错过了一个基本点!

  • 创建一个接口,该接口将返回一个代表 XML 的代理对象。
  • 实现接口两次,一次使用 WebRequest,另一次返回静态“响应”。
  • 接口实现然后根据响应或静态 XML 实例化返回类型。
  • 然后您可以在测试或生产时将所需的类传递给服务层。

敲完代码后,我会粘贴一些示例。

【问题讨论】:

  • 嗨,Rob,你搞定了吗?我刚刚遇到这个问题,作为一个单元测试新手,我很想看看你是如何解决这个问题的。
  • 嗨,尼克,就像在更新中一样。我最终做的是针对接口进行编程。然后我实现了两次接口。一种返回静态内容,一种实际使用 WebRequest。然后,您可以对消费者进行 UT,然后只需使用您测试过的消费者类的实际安全知识。永远记住,“不要测试别人的代码”(例如 WebRequest)。

标签: xml web-services rest webrequest webresponse


【解决方案1】:

我在想做完全相同的事情时发现了这个问题。无法在任何地方找到答案,但经过更多挖掘后发现 .Net Framework 已内置对此的支持。

您可以使用WebRequest.RegisterPrefix 注册一个工厂对象,WebRequest.Create 在使用该前缀(或 url)时将调用该对象。工厂对象必须实现IWebRequestCreate,它有一个方法Create,它返回一个WebRequest。在这里你可以返回你的模拟WebRequest

我已经把一些示例代码放在 http://blog.salamandersoft.co.uk/index.php/2009/10/how-to-mock-httpwebrequest-when-unit-testing/

【讨论】:

  • 这是在您的客户端代码开始检查 HttpWebResponse 以查看状态代码之前要走的路……那么您需要在下面查看我的答案!
  • 我唯一想不通的是如何处理两个连续的网络请求......
【解决方案2】:

这是一个不需要模拟的解决方案。您实现了WebRequest 的所有三个组件:IWebRequestCreateWebRequestWebResponse。见下文。我的示例生成失败的请求(通过抛出 WebException),但应该能够对其进行调整以发送“真实”响应:

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

您必须创建一个HttpWebResponse 子类,否则您无法创建一个。

棘手的部分(在GetException() 方法中)是输入您无法覆盖的值,例如StatusCode 这就是我们最好的伙伴SerializaionInfo 的用武之地!这是您提供无法覆盖的值的地方。显然,覆盖(HttpWebResponse 的)您可以的部分,以完成剩下的工作。

我是如何在所有AddValue() 调用中获得“名称”的?从异常消息!很高兴能轮流告诉我每个人,直到我开心为止。

现在,编译器会抱怨“过时”,但这仍然有效,包括 .NET Framework 版本 4。

这是一个(通过)测试用例供参考:

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

【讨论】:

  • 在 .net 4.6 之前,这确实是一种为单元测试目的“伪造”网络响应的好方法,但它在 .netstandard 中不起作用,因为“过时”构造函数已被删除。跨度>
【解决方案3】:

我之前发现了以下博客,它解释了使用 Microsoft Moles 的一个很好的方法。

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

简而言之,解决方案建议如下:

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

【讨论】:

    【解决方案4】:

    你不能。最好的办法是将它包装在一个代理对象中,然后模拟它。或者,您必须使用可以拦截无法模拟的类型的模拟框架,例如 TypeMock。但你说的是美元,那里。最好做一点包装。


    显然你可以做一些额外的工作。在此处查看投票最高的答案。

    【讨论】:

    • 您能否澄清一下我如何在不影响核心代码的情况下“包装”?调用 GetResponse 后,砰!关闭它运行.. 显然我需要做一些依赖注入,但不知道我怎样才能得到我需要的对象,你知道吗?
    • 保护它,是个白痴。我和你在一起。创建代理对象来携带结果,调用的接口。在两个类中实现:1 = WebRequest,2 = 静态对象)。现在有道理了,谢谢你扇了我耳光让我明白了,我知道我看不到树林里的树林:)
    • 这是不正确的,因为下面理查德威利斯的回答显示了如何做到这一点。可以使用他链接到的代码来模拟 WebResponse。
    • @tmont 很有趣,并向威利斯 +1。为清晰起见进行了编辑。很好,他们把它藏得很好......
    【解决方案5】:

    这不是一个完美的解决方案,但它以前对我有用,值得特别注意简单性:

    HTTPSimulator

    还有一个 typemock 示例记录在 typemock forums:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-21
      • 2015-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-17
      • 2011-10-20
      相关资源
      最近更新 更多