C# 是否可以模拟 .NET HttpWebResponse?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9823039/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it possible to mock out a .NET HttpWebResponse?
提问by Pure.Krome
i've got an integration test that grabs some json result from a 3rd party server. It's really simple and works great.
我有一个集成测试,可以从 3rd 方服务器获取一些 json 结果。这真的很简单,而且效果很好。
I was hoping to stop actually hitting this server and using Moq(or any Mocking library, like ninject, etc) to hiHyman and force the return result.
我希望停止实际Moq攻击此服务器并使用(或任何 Mocking 库,如 ninject 等)来劫持并强制返回结果。
is this possible?
这可能吗?
Here is some sample code :-
这是一些示例代码:-
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..
}
and of course, this method is called from my test.
当然,这个方法是从我的测试中调用的。
I was thinking that maybe I need to pass into this method (or a property of the class?) a mocked httpWebResponseor something but wasn't too sure if this was the way. Also, the response is a output from an httpWebRequest.GetResponse()method .. so maybe I just need to pass in a mocked HttpWebRequest?.
我在想,也许我需要传入这个方法(或类的属性?)一个嘲笑httpWebResponse或其他东西,但不太确定这是不是这样。此外,响应是httpWebRequest.GetResponse()方法的输出.. 所以也许我只需要传入一个模拟的HttpWebRequest? 。
any suggestions with some sample code would be most aprreciated!
对一些示例代码的任何建议将是最有价值的!
采纳答案by moribvndvs
You may wish to change your consuming code to take in an interface for a factory that creates requests and responses that can be mocked which wrap the actual implementation.
您可能希望更改您的消费代码以接收工厂的接口,该接口创建可以模拟的请求和响应,这些请求和响应包装了实际的实现。
Update: Revisiting
更新:重温
I've been getting downvotes long after my answer was accepted, and I admit my original answer was poor quality and made a big assumption.
在我的答案被接受后很长一段时间内,我的票数一直在下降,我承认我最初的答案质量很差,并且做了一个很大的假设。
Mocking HttpWebRequest in 4.5+
在 4.5+ 中模拟 HttpWebRequest
The confusion from my original answer lies in the fact that you can mock HttpWebResponsein 4.5, but not earlier versions. Mocking it in 4.5 also utilizes obsolete constructors. So, the recommended course of action is to abstract the request and response. Anyways, below is a complete working test using .NET 4.5 with Moq 4.2.
我的原始答案的混淆在于您可以HttpWebResponse在 4.5 中模拟,但不能在早期版本中模拟。在 4.5 中模拟它也使用过时的构造函数。因此,推荐的操作过程是抽象请求和响应。无论如何,下面是使用 .NET 4.5 和 Moq 4.2 的完整工作测试。
[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);
}
Better answer: Abstract the Response and Request
更好的答案:抽象响应和请求
Here's a safer bare-bones implementation of an abstraction that will work for prior versions (well, down to 3.5 at least):
这是一个更安全的抽象的基本实现,适用于以前的版本(好吧,至少到 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();
}
}
回答by David Basarab
Rather than mocking out the HttpWebResponse is I would wrap the call behind an interface, and mock that interface.
我不是模拟 HttpWebResponse 而是将调用包装在接口后面,并模拟该接口。
If you are testing does the web response hit the site I want it too, that is a different test than if does class A call the WebResponse interface to get the needed data.
如果您正在测试 Web 响应是否也访问了我想要的站点,这与 A 类是否调用 WebResponse 接口以获取所需数据的测试不同。
For mocking an interface I prefer Rhino mocks. See hereon how to use it.
对于模拟界面,我更喜欢Rhino mocks。请参阅此处了解如何使用它。
回答by Aliostad
None of the Microsoft's HTTP stack was developed with unit testing and separation in mind.
Microsoft 的 HTTP 堆栈在开发时都没有考虑到单元测试和分离。
You have three options:
您有三个选择:
- Make the call to web as small as possible (i.e. send and get back data and pass to other methods) and test the rest. As far as the web call is concerned, there should be much magic happening there and very straightforward.
- Wrap the HTTP call in another class and pass your mock object while testing.
- Wrap
HttpWebResponseandHttpWebRequestby two other classes. This is what the MVC team did withHttpContext.
- 使对 web 的调用尽可能小(即发送和取回数据并传递给其他方法)并测试其余部分。就网络调用而言,那里应该有很多神奇的东西,而且非常简单。
- 将 HTTP 调用包装在另一个类中并在测试时传递模拟对象。
- Wrap
HttpWebResponse和HttpWebRequest另外两个类。这就是 MVC 团队对HttpContext.
Second option:
第二种选择:
interface IWebCaller
{
string CallWeb(string address);
}
回答by Mauricio Scheffer
回答by escape-llc
You can actually return HttpWebResponsewithout mocking, see my answer here. It does not require any "external" proxying interfaces, only the "standard" WebRequestWebResponseand ICreateWebRequest.
您实际上可以在HttpWebResponse不嘲笑的情况下返回,请在此处查看我的答案。它不需要任何“外部”代理接口,只需要“标准”WebRequestWebResponse和ICreateWebRequest.
If you don't need access to HttpWebResponseand can deal with just WebResponseit is even easier; we do that in our unit tests to return "prefabricated" content responses for consumption. I had to "go the extra mile" in order to return actual HTTP status codes, to simulate e.g. 404 responses which requires you use HttpWebResponseso you can access the StatusCodeproperty et al.
如果您不需要访问HttpWebResponse并且可以处理WebResponse它,那就更容易了;我们在单元测试中这样做是为了返回“预制”的内容响应以供消费。我不得不“加倍努力”以返回实际的 HTTP 状态代码,以模拟例如需要您使用的 404 响应,HttpWebResponse以便您可以访问StatusCode属性等。
The other solutions assuming everything is HttpWebXXXignores everything supported by WebRequest.Create()except HTTP, which can be a handler for any registered prefix you care to use (via WebRequest.RegisterPrefix()and if you are ignoring that, you are missing out, because it is a great way to expose other content streams you otherwise have no way to access, e.g. Embeeded Resource Streams, File streams, etc.
假设一切都被HttpWebXXX忽略WebRequest.Create()的其他解决方案会忽略除了 HTTP支持的所有内容,它可以是您想使用的任何已注册前缀的处理程序(通过WebRequest.RegisterPrefix(),如果您忽略了这一点,您就错过了,因为这是公开其他内容的好方法您无法访问的流,例如嵌入资源流、文件流等。
Also, explicitly casting the return of WebRequest.Create()to HttpWebRequestis a path to breakage, since the method return type is WebRequestand again, shows some ignorance of how that API actually works.
此外,显式转换WebRequest.Create()to的返回HttpWebRequest是破坏的路径,因为方法返回类型是WebRequest并且再次显示出对该 API 实际工作方式的一些无知。
回答by Guilherme Torres Castro
I Found a great solution in this blog post:
我在这篇博文中找到了一个很好的解决方案:
It′s very easy to use, you just need to do this:
它非常易于使用,您只需要执行以下操作:
string response = "my response string here";
WebRequest.RegisterPrefix("test", new TestWebRequestCreate());
TestWebRequest request = TestWebRequestCreate.CreateTestRequest(response);
And copy those files to your project:
并将这些文件复制到您的项目中:
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;
}
}
回答by David Hall
If it helps please find below the code illustrated in the accepted answer using NSubstitute in place of Moq
如果有帮助,请使用 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 Test run and passes successfully.
单元测试运行并成功通过。
Up vote given on the answer I've been looking for some time how to do this effectively.
我一直在寻找如何有效地做到这一点的答案。

