代码之家  ›  专栏  ›  技术社区  ›  Rob Cooper

从WebRequest模拟WebResponse

  •  33
  • Rob Cooper  · 技术社区  · 17 年前

    我终于开始费劲地创建一些与RESTful Web界面一起工作的应用程序,但是,我担心每次我点击F5运行一系列测试时,我都会敲打它们的服务器。

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

    但是,我不知道如何“模拟”Web响应,因为(afaik)它们只能由 Web请求.getResponse

    你们怎么嘲笑这种事?你…吗?我只是真的不喜欢我锤击他们的服务器:s我不想更改代码 很多,但我希望有一种优雅的方式来做到这一点。

    接受后更新

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

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

    一旦我把代码整理好,我就粘贴一些样本。

    5 回复  |  直到 17 年前
        1
  •  59
  •   Despertar    12 年前

    我在做完全相同的事情时发现了这个问题。在任何地方都找不到答案,但经过一番挖掘,发现.NET框架内置了对此的支持。

    可以用注册工厂对象 WebRequest.RegisterPrefix 哪一个 WebRequest.Create 使用该前缀(或URL)时将调用。工厂对象必须实现 IWebRequestCreate 只有一种方法 Create 返回一个 WebRequest . 在这里你可以退回你的模型 WebREST .

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

        2
  •  14
  •   escape-llc    13 年前

    这里有一个不需要模拟的解决方案。您可以实现 WebRequest : IWebRequestCreate WebREST WebResponse . 见下文。我的示例生成失败的请求(通过抛出 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 进来!这是您提供不能重写的值的地方。显然,覆盖 HTTPWebRead )你能,把剩下的路开到那里。

    我是如何获得所有这些“名字”的 AddValue() 电话?来自异常消息!我很高兴能依次告诉我每一件事,直到我让它高兴为止。

    现在,编译器会抱怨“过时”,但这仍然有效,包括.NET框架版本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;
            }
        }
    
        3
  •  3
  •   Will    16 年前

    你不能。最好把它包装在代理对象中,然后模仿它。或者,您必须使用一个模拟框架来拦截不能模拟的类型,比如typemock。但你说的是美元。最好做一点包装。


    很显然你 可以 有点额外的工作。在这里检查投票最高的答案。

        4
  •  2
  •   Simon Parsons    15 年前

    我在前面找到了下面的博客,它解释了使用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.");
        }
    
        5
  •  0
  •   dr. evil    17 年前

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

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