从WebRequest模拟WebResponse

Rob*_*per 34 xml rest web-services webrequest webresponse

我终于开始讨论创建一些与RESTful Web界面一起使用的应用程序,但是,我担心每次按F5运行一系列测试时我都在锤击他们的服务器.

基本上,我需要获得一系列的Web响应,以便我可以测试我正确解析变化的响应,而不是每次都打到它们的服务器,我想我可以这样做一次,保存XML然后在本地工作.

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

你们怎么去嘲笑这种事情?你呢?我真的不喜欢我正在锤击他们的服务器的事实:SI不想过多地改变代码,但我希望有一种优雅的方式来做这件事.

接受后更新

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

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

一旦我敲了代码,我就会粘贴一些样本.

小智 59

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

您可以注册一个工厂对象与WebRequest.RegisterPrefixWebRequest.Create使用的前缀(或URL)时,将调用.工厂对象必须实现IWebRequestCreate哪个具有Create返回a 的单个方法WebRequest.在这里你可以返回你的模拟WebRequest.

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

  • 这是一种方法,直到你的客户端代码开始检查`HttpWebResponse`来查看状态代码...然后你需要在下面看到我的答案! (7认同)

esc*_*llc 14

这是一个不需要模拟的解决方案.您实现了以下所有三个组件WebRequest:IWebRequestCreate WebRequestWebResponse.见下文.我的示例生成失败请求(通过抛出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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

您必须创建一个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;
        }
    }
Run Code Online (Sandbox Code Playgroud)