是否有可能嘲笑一个.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时传递你的模拟对象。
- 用另外两个类来包装
HttpWebResponse
和HttpWebRequest
。 这是MVC团队用HttpContext
所做的。
第二个选项:
interface IWebCaller { string CallWeb(string address); }
你可以实际返回HttpWebResponse
而不嘲笑, 在这里看到我的答案。 它不需要任何“外部”代理接口,只需要“标准的” WebRequest
WebResponse
和ICreateWebRequest
。
如果你不需要访问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; } }