是否可以模拟 .NET HttpWebResponse?
Posted
技术标签:
【中文标题】是否可以模拟 .NET HttpWebResponse?【英文标题】:Is it possible to mock out a .NET HttpWebResponse? 【发布时间】:2012-04-07 01:25:07 【问题描述】:我有一个集成测试,可以从第三方服务器获取一些 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..
当然,这个方法是从我的测试中调用的。
我在想也许我需要传递给这个方法(或类的属性?)一个模拟的httpWebResponse
或其他东西,但不太确定这是否是方法。此外,响应是httpWebRequest.GetResponse()
方法的输出......所以也许我只需要传入一个模拟的HttpWebRequest
?.
任何带有示例代码的建议都将受到高度重视!
【问题讨论】:
【参考方案1】:与其模拟 HttpWebResponse,不如将调用封装在接口后面,然后模拟该接口。
如果您正在测试 Web 响应是否也命中了我想要的站点,这与 A 类是否调用 WebResponse 接口以获取所需数据的测试不同。
对于模拟界面,我更喜欢Rhino mocks。使用方法见here。
【讨论】:
方法(上图)实际上是一个隐含的接口。所以班级是public class AwesomeClass : IThinkICan
。所以你建议我只是模拟那个类,所以我模拟返回对象?现在,我的测试确实击中了第 3 方,但我不希望它这样做。 (即从集成测试转到单元测试)。【参考方案2】:
Microsoft 的 HTTP 堆栈在开发时都没有考虑到单元测试和分离。
您有三个选择:
使对 Web 的调用尽可能小(即发送和取回数据并传递给其他方法)并测试其余部分。就网络通话而言,那里应该发生很多神奇的事情,而且非常简单。 将 HTTP 调用封装在另一个类中,并在测试时传递您的模拟对象。 用另外两个类包装HttpWebResponse
和HttpWebRequest
。这就是 MVC 团队对 HttpContext
所做的事情。
第二个选项:
interface IWebCaller
string CallWeb(string address);
【讨论】:
【参考方案3】:您可能希望更改您的消费代码以接收一个工厂接口,该接口创建可以模拟的请求和响应,这些请求和响应包装了实际的实现。
更新:重温
在我的答案被接受后很长一段时间内,我一直在投票,我承认我最初的答案质量很差,并且做了很大的假设。
在 4.5+ 中模拟 HttpWebRequest
我原来的答案的困惑在于你可以在 4.5 中模拟 HttpWebResponse
,但不能在早期版本中模拟。在 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);
更好的答案:抽象响应和请求
这是一个更安全的抽象实现,适用于以前的版本(嗯,至少低至 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 创建模拟。您将收到如下错误:“System.ArgumentException:无法实例化类的代理:System.Net.HttpWebResponse。找不到无参数构造函数。参数名称:constructorArguments” 我找不到支持 Is IHttpWebRequest、IHttpWebRequestFactory、IHttpWebResponse 的 .Net 框架。有什么想法吗? @AjitGoel 它们是您必须创建的接口,我在示例中提供了这些接口。向下滚动第二个代码示例,它就在那里。 @HackedByChinese:不知道我是怎么错过的。需要咖啡来唤醒我。 使用 IHttpWebRequestFactory 有充分的理由吗?我们不能把 Create 方法保留在 IHttpWebRequest 接口中吗?【参考方案4】:您实际上可以在不嘲笑的情况下返回HttpWebResponse
,请参阅我的回答here。它不需要任何“外部”代理接口,只需要“标准”WebRequest
WebResponse
和ICreateWebRequest
。
如果您不需要访问HttpWebResponse
而只需处理WebResponse
,那就更容易了;我们在单元测试中这样做是为了返回“预制”内容响应以供消费。我必须“加倍努力”才能返回实际的 HTTP 状态代码,以模拟例如404 响应要求您使用 HttpWebResponse
,以便您可以访问 StatusCode
属性等。
假设一切都是HttpWebXXX
的其他解决方案忽略WebRequest.Create()
支持的所有东西,除了HTTP,它可以是你想使用的任何注册前缀的处理程序(通过WebRequest.RegisterPrefix()
,如果你忽略这一点,您就错过了,因为它是公开其他您无法访问的内容流的好方法,例如嵌入资源流、文件流等。
此外,将WebRequest.Create()
的返回显式转换为HttpWebRequest
是破坏路径,因为方法返回类型是WebRequest
,并且再次表明对该API 的实际情况有些无知有效。
【讨论】:
【参考方案5】:我在blog post 中找到了一个很好的解决方案:
它非常易于使用,您只需要这样做:
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;
【讨论】:
这没有引用 HttpWebResponse。一旦您将 TestWebResponse 转换为 HttpWebResponse ,它将为空。 @Christo 当您调用WebRequest.RegisterPrefix("test", new TestWebRequestCreate());
时,您正在为请求注册前缀。所以你也必须模拟 url:使用 test://example.com 而不是 http://example.com
您发现的帖子很酷,对于测试 WebRequest/Response 等很有用。但最初的问题是我如何测试 HttpWebRequest
。如果您使用此代码测试问题中的代码,则尝试将TestWebReponse
显式转换为HttpWebRequest
将失败。他方法的第 4 行
@Christo 我认为您的意思是将TestWebReponse
转换为HttpWebResponse
和TestWebRequest
转换为HttpWebRequest
。在这种情况下,您只需将继承自 TestWebRequest
和 TestWebReponse
更改为分别继承自 HttpWebRequest
和 HttpWebResponse
抱歉错字。我确实是那个意思。我建议您测试这些建议,因为我认为这行不通。【参考方案6】:
如果有帮助,请在下面使用 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);
单元测试运行并成功通过。
我一直在寻找如何有效地做到这一点的答案。
【讨论】:
投反对票,因为这根本不起作用:var r = Substitute.For<HttpWebResponse>(); Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: Can not instantiate proxy of class: System.Net.HttpWebResponse.
该死,无法编辑我的评论,所以将添加另一个:您不能使用此类的 .Net 3.5 变体的替代品。更高版本有默认构造函数。但在更高版本中,默认构造函数也是公共的,因此您可以简单地实例化此类而不是模拟【参考方案7】:
虽然有一个公认的答案,但我想分享我的观点。很多时候,修改源代码并不是建议的选择。此外,答案至少引入了很多生产代码。
所以我更喜欢使用免费的harmonyLib 并通过拦截WebRequest.Create
静态方法来修补CLR,而不需要对生产代码进行任何更改。
我修改后的版本如下:
public static bool CreateWithSomeContent(ref WebRequest __result)
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 requestMock = new Mock<HttpWebRequest>();
requestMock.Setup(c => c.GetResponse()).Returns(response.Object);
__result = requestMock.Object;
return false;
[Test]
public void Create_should_create_request_and_respond_with_stream()
//arrange
var harmony = new Harmony(nameof(Create_should_create_request_and_respond_with_stream));
var methodInfo = typeof(WebRequest).GetMethod("Create",new Type[]typeof(string));
harmony.Patch(methodInfo, new HarmonyMethod(GetType(), nameof(CreateWithSomeContent)));
//act
var actualRequest = WebRequest.Create("");
string actual;
using (var httpWebResponse = (HttpWebResponse)actualRequest.GetResponse())
using (var streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
actual = streamReader.ReadToEnd();
// assert
// actual.Should().Be("response content");
【讨论】:
以上是关于是否可以模拟 .NET HttpWebResponse?的主要内容,如果未能解决你的问题,请参考以下文章