是否有可能嘲笑一个.NET HttpWebResponse?

我有一个集成testing,从第三方服务器抓取一些JSON结果。 这非常简单,效果很好。

我希望能够实际上停止使用这个服务器,并使用Moq (或任何Mocking库,比如ninject等)来劫持和强制返回结果。

这可能吗?

这是一些示例代码:

 public Foo GoGetSomeJsonForMePleaseKThxBai() { // prep stuff ... // Now get json please. HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("Http://some.fancypants.site/api/hiThere); httpWebRequest.Method = WebRequestMethods.Http.Get; string responseText; using (var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { json = streamReader.ReadToEnd().ToLowerInvariant(); } } // Check the value of the json... etc.. } 

当然,这个方法是从我的testing中调用的。

我在想,也许我需要传递给这个方法(或类的属性?)一个嘲弄的httpWebResponse什么的,但不太确定,如果这样的话。 此外,响应是从httpWebRequest.GetResponse()方法的输出..所以也许我只需要传递一个httpWebRequest.GetResponse() HttpWebRequest

一些示例代码的任何build议将是最重要的!

您可能希望更改您的使用代码,以便为工厂创build一个接口,以创build可以模拟实际实现的请求和响应。

更新:重温

我的回答被接受了很久以后,我一直在听取低估,我承认我原来的答案质量差,做出了一个很大的假设。

嘲笑HttpWebRequest 4.5 +

我原来的答案困惑在于,你可以嘲笑HttpWebResponse在4.5,但不是早期版本。 在4.5中嘲笑它也使用了过时的构造函数。 因此,推荐的行动scheme是提取请求和回应。 无论如何,下面是使用.NET 4.5与Moq 4.2完成一个完整的工作testing。

 [Test] public void Create_should_create_request_and_respond_with_stream() { // arrange var expected = "response content"; var expectedBytes = Encoding.UTF8.GetBytes(expected); var responseStream = new MemoryStream(); responseStream.Write(expectedBytes, 0, expectedBytes.Length); responseStream.Seek(0, SeekOrigin.Begin); var response = new Mock<HttpWebResponse>(); response.Setup(c => c.GetResponseStream()).Returns(responseStream); var request = new Mock<HttpWebRequest>(); request.Setup(c => c.GetResponse()).Returns(response.Object); var factory = new Mock<IHttpWebRequestFactory>(); factory.Setup(c => c.Create(It.IsAny<string>())) .Returns(request.Object); // act var actualRequest = factory.Object.Create("http://www.google.com"); actualRequest.Method = WebRequestMethods.Http.Get; string actual; using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { actual = streamReader.ReadToEnd(); } } // assert actual.Should().Be(expected); } public interface IHttpWebRequestFactory { HttpWebRequest Create(string uri); } 

更好的回答:摘要回应和要求

这是一个比较安全的抽象实现,它可以用于以前的版本(至less下降到3.5):

 [Test] public void Create_should_create_request_and_respond_with_stream() { // arrange var expected = "response content"; var expectedBytes = Encoding.UTF8.GetBytes(expected); var responseStream = new MemoryStream(); responseStream.Write(expectedBytes, 0, expectedBytes.Length); responseStream.Seek(0, SeekOrigin.Begin); var response = new Mock<IHttpWebResponse>(); response.Setup(c => c.GetResponseStream()).Returns(responseStream); var request = new Mock<IHttpWebRequest>(); request.Setup(c => c.GetResponse()).Returns(response.Object); var factory = new Mock<IHttpWebRequestFactory>(); factory.Setup(c => c.Create(It.IsAny<string>())) .Returns(request.Object); // act var actualRequest = factory.Object.Create("http://www.google.com"); actualRequest.Method = WebRequestMethods.Http.Get; string actual; using (var httpWebResponse = actualRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { actual = streamReader.ReadToEnd(); } } // assert actual.Should().Be(expected); } public interface IHttpWebRequest { // expose the members you need string Method { get; set; } IHttpWebResponse GetResponse(); } public interface IHttpWebResponse : IDisposable { // expose the members you need Stream GetResponseStream(); } public interface IHttpWebRequestFactory { IHttpWebRequest Create(string uri); } // barebones implementation private class HttpWebRequestFactory : IHttpWebRequestFactory { public IHttpWebRequest Create(string uri) { return new WrapHttpWebRequest((HttpWebRequest)WebRequest.Create(uri)); } } public class WrapHttpWebRequest : IHttpWebRequest { private readonly HttpWebRequest _request; public WrapHttpWebRequest(HttpWebRequest request) { _request = request; } public string Method { get { return _request.Method; } set { _request.Method = value; } } public IHttpWebResponse GetResponse() { return new WrapHttpWebResponse((HttpWebResponse)_request.GetResponse()); } } public class WrapHttpWebResponse : IHttpWebResponse { private WebResponse _response; public WrapHttpWebResponse(HttpWebResponse response) { _response = response; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { if (_response != null) { ((IDisposable)_response).Dispose(); _response = null; } } } public Stream GetResponseStream() { return _response.GetResponseStream(); } } 

几年前, 我为此写了一组接口和适配器 。

而不是嘲笑HttpWebResponse是我将包裹在一个接口后面的调用,并模拟该接口。

如果您正在testingnetworking响应是否也触发了我想要的网站,那么与A类调用WebResponse接口获取所需数据的情况相比,这是一个不同的testing。

嘲笑一个界面,我更喜欢犀牛嘲笑 。 在这里看到如何使用它。

如果有帮助,请使用NSubstitute代替Moq在接受的答案中说明代码

 using NSubstitute; /*+ other assemblies*/ [TestMethod] public void Create_should_create_request_and_respond_with_stream() { //Arrange var expected = "response content"; var expectedBytes = Encoding.UTF8.GetBytes(expected); var responseStream = new MemoryStream(); responseStream.Write(expectedBytes, 0, expectedBytes.Length); responseStream.Seek(0, SeekOrigin.Begin); var response = Substitute.For<HttpWebResponse>(); response.GetResponseStream().Returns(responseStream); var request = Substitute.For<HttpWebRequest>(); request.GetResponse().Returns(response); var factory = Substitute.For<IHttpWebRequestFactory>(); factory.Create(Arg.Any<string>()).Returns(request); //Act var actualRequest = factory.Create("http://www.google.com"); actualRequest.Method = WebRequestMethods.Http.Get; string actual; using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse()) { using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream())) { actual = streamReader.ReadToEnd(); } } //Assert Assert.AreEqual(expected, actual); } public interface IHttpWebRequestFactory { HttpWebRequest Create(string uri); } 

unit testing运行并成功通过。

投票给出的答案我一直在寻找一些时间如何有效地做到这一点。

没有一个微软的HTTP协议栈是用unit testing和分离开发的。

你有三个select:

  • 打电话到networking尽可能小(即发送和取回数据,并传递给其他方法),并testing其余的。 就networking电话而言,应该有很多魔术发生在那里,非常简单。
  • 将HTTP调用包装在另一个类中,并在testing时传递你的模拟对象。
  • 用另外两个类来包装HttpWebResponseHttpWebRequest 。 这是MVC团队用HttpContext所做的。

第二个选项:

 interface IWebCaller { string CallWeb(string address); } 

你可以实际返回HttpWebResponse而不嘲笑, 在这里看到我的答案。 它不需要任何“外部”代理接口,只需要“标准的” WebRequest WebResponseICreateWebRequest

如果你不需要访问HttpWebResponse并且只需要处理WebResponse就更容易; 我们在unit testing中做到这一点,以返回“预制”的内容响应消费。 为了返回实际的HTTP状态代码,我必须“多走一步”来模拟例如404响应,这需要您使用HttpWebResponse以便您可以访问StatusCode属性等。

假设一切的其他解决scheme是HttpWebXXX 忽略WebRequest.Create()除了HTTP ,它可以是任何注册的前缀,你喜欢使用的处理程序(通过WebRequest.RegisterPrefix() ,如果你忽略了,你错过了,因为这是暴露其他内容stream的好方法,否则您无法访问,例如Embeeded Resource Streams,File streams等。

另外,显式地将WebRequest.Create()返回给HttpWebRequest一个破坏的途径 ,因为方法的返回types是WebRequest并且再次显示了对这个API实际工作的一些无知。

我在这篇博文中find了一个很好的解决scheme:

这很容易使用,你只需要这样做:

 string response = "my response string here"; WebRequest.RegisterPrefix("test", new TestWebRequestCreate()); TestWebRequest request = TestWebRequestCreate.CreateTestRequest(response); 

并将这些文件复制到您的项目:

  class TestWebRequestCreate : IWebRequestCreate { static WebRequest nextRequest; static object lockObject = new object(); static public WebRequest NextRequest { get { return nextRequest ;} set { lock (lockObject) { nextRequest = value; } } } /// <summary>See <see cref="IWebRequestCreate.Create"/>.</summary> public WebRequest Create(Uri uri) { return nextRequest; } /// <summary>Utility method for creating a TestWebRequest and setting /// it to be the next WebRequest to use.</summary> /// <param name="response">The response the TestWebRequest will return.</param> public static TestWebRequest CreateTestRequest(string response) { TestWebRequest request = new TestWebRequest(response); NextRequest = request; return request; } } class TestWebRequest : WebRequest { MemoryStream requestStream = new MemoryStream(); MemoryStream responseStream; public override string Method { get; set; } public override string ContentType { get; set; } public override long ContentLength { get; set; } /// <summary>Initializes a new instance of <see cref="TestWebRequest"/> /// with the response to return.</summary> public TestWebRequest(string response) { responseStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(response)); } /// <summary>Returns the request contents as a string.</summary> public string ContentAsString() { return System.Text.Encoding.UTF8.GetString(requestStream.ToArray()); } /// <summary>See <see cref="WebRequest.GetRequestStream"/>.</summary> public override Stream GetRequestStream() { return requestStream; } /// <summary>See <see cref="WebRequest.GetResponse"/>.</summary> public override WebResponse GetResponse() { return new TestWebReponse(responseStream); } } class TestWebReponse : WebResponse { Stream responseStream; /// <summary>Initializes a new instance of <see cref="TestWebReponse"/> /// with the response stream to return.</summary> public TestWebReponse(Stream responseStream) { this.responseStream = responseStream; } /// <summary>See <see cref="WebResponse.GetResponseStream"/>.</summary> public override Stream GetResponseStream() { return responseStream; } }