关于xml:从WebRequest中模拟WebResponse

关于xml:从WebRequest中模拟WebResponse

Mocking WebResponse's from a WebRequest

我终于开始创建一些可以与RESTful Web界面一起使用的应用程序了,但是,我担心每次我按F5来运行一系列测试时,我都会锤打他们的服务器。

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

但是,我看不到如何"模拟" WebResponse,因为(AFAIK)它们只能由WebRequest.GetResponse实例化。

你们如何嘲笑这种事情?你呢?我只是真的不喜欢我要锤打他们的服务器:S我不想过多更改代码,但是我希望有一种优雅的方法。

接受后更新

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

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

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


我在寻求做完全相同的事情时发现了这个问题。在任何地方都找不到答案,但经过进一步挖掘后发现,.Net Framework已内置对此功能的支持。

您可以使用WebRequest.RegisterPrefix注册工厂对象,当使用该前缀(或URL)时,WebRequest.Create将调用该对象。工厂对象必须实现具有单个方法CreateIWebRequestCreate,该方法返回WebRequest。在这里,您可以返回模拟WebRequest

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


这是不需要模拟的解决方案。您实现WebRequest的所有三个组件:IWebRequestCreate WebRequestWebResponse。见下文。我的示例生成失败的请求(通过抛出WebException),但应该能够使其适应发送"真实"响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    [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;
        }
    }


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

Mocking HttpWebResponse with Moles

简而言之,该解决方案建议以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
    [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>

"
+
                            "  <head></head>

"
+
                            "  <body>

"
+
                            "     Hello World

"
+
                            "  </body>

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

你不能最好的办法是将其包装在代理对象中,然后对其进行模拟。另外,您必须使用一个模拟框架,该框架可以拦截无法模拟的类型,例如TypeMock。但是,您在谈论的是雄鹿。最好做一点包裹。

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


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

HTTP模拟器

也是typemock论坛中记录的typemock示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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);
    }
  }
}


推荐阅读

    linux运行图形界命令?

    linux运行图形界命令?,系统,密码,地址,电脑,图形界面,地方,工具,界面,终端,

    linux怎样运行命令?

    linux怎样运行命令?,系统,工作,信息,基础,地址,命令,目录,工具,密码,一致,Lin

    linux编译完运行命令?

    linux编译完运行命令?,系统,代码,环境,工具,信息,命令,文件,程序,终端,编辑,

    linux命令程序运行?

    linux命令程序运行?,状态,系统,服务,情况,命令,进程,软件,数据,发行,时间,Lin

    linux界面网络命令?

    linux界面网络命令?,网络,工作,地址,系统,信息,命令,目录,管理,标准,状态,使

    linux运行多个命令?

    linux运行多个命令?,环境,软件,系统,工作,服务,连续,命令,指令,分号,冲突,lin

    linux命令行界面向上?

    linux命令行界面向上?,系统,标准,工作,地址,环境,命令,管理,服务,较大,位置,l

    linux炫酷命令界面?

    linux炫酷命令界面?,系统,环境,项目,在线,网络,基础知识,网上,网站,近几年,

    linux运行命令查看?

    linux运行命令查看?,系统,信息,状态,命令,名称,情况,地址,软件,进程,第一,lin

    linux中命令运行软件?

    linux中命令运行软件?,软件,系统,名称,工具,电脑,位置,环境,中心,在线,初级,

    脚本linux上运行命令?

    脚本linux上运行命令?,工具,代码,时间,密码,系统,环境,名字,位置,第三,下来,t

    linux命令界面输入法?

    linux命令界面输入法?,系统,工具,信息,第三,软件,输入法,名字,盘后,语言,区

    linux转为命令界面?

    linux转为命令界面?,系统,密码,电脑,软件,界面,图形界面,控制台,终端,命令,

    linux命令行界面汉化?

    linux命令行界面汉化?,系统,标准,软件,网上,管理,工具,电脑,底部,服务,项目,x

    linux运行命令的脚本?

    linux运行命令的脚本?,系统,服务,工具,脚本,意外,技术,分析,文件,方法,命令,s

    linux界面输入命令?

    linux界面输入命令?,工作,系统,信息,地址,平台,软件,位置,环境,盘中,设计,lin

    linux命令行界面翻页?

    linux命令行界面翻页?,工具,系统,命令,占比,技术,进程,空格键,模式,文件,字

    linux影藏运行命令?

    linux影藏运行命令?,档案,电脑,标准,设备,代码,工具,系统,查询系统,暂停,命

    linux命令界面切换?

    linux命令界面切换?,密码,系统,终端,信息,状态,环境,地方,电脑,工具,命令,lin