是否可以模拟出.NET HttpWebResponse
本文关键字:NET HttpWebResponse 模拟 是否 | 更新日期: 2023-09-27 18:28:44
我有一个集成测试,可以从第三方服务器获取一些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
?
任何带有一些示例代码的建议都将是最合适的!
您可能希望更改使用代码,以采用工厂的接口,该接口创建可以模拟的请求和响应,从而包装实际实现。
更新:重新审视
在我的答案被接受很久之后,我一直在获得反对票,我承认我最初的答案质量很差,并且做出了很大的假设。
在 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 ,不如将调用包装在一个接口后面,并模拟该接口。
如果您正在测试 Web 响应是否命中了我也想要的站点,这与 A 类调用 WebResponse 接口以获取所需数据是不同的测试。
对于模拟界面,我更喜欢 Rhino 模拟。 请参阅此处了解如何使用它。
Microsoft的HTTP堆栈都没有考虑到单元测试和分离。
您有三种选择:
- 使对 web 的调用尽可能小(即发送和取回数据并传递给其他方法(并测试其余部分。就网络通话而言,那里应该有很多魔术发生,而且非常简单。 将
- HTTP 调用包装在另一个类中,并在测试时传递模拟对象。
- 用另外两个类包装
HttpWebResponse
和HttpWebRequest
。这就是MVC团队对HttpContext
所做的。
第二种选择:
interface IWebCaller
{
string CallWeb(string address);
}
如果有帮助,请在下面找到接受答案中所示的代码,使用 NSubstitute 代替最小起订量
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);
}
单元测试运行并成功通过。
对我一直在寻找的答案投赞成票 如何有效地做到这一点。
你实际上可以返回HttpWebResponse
而不会嘲笑,请在此处查看我的答案。 它不需要任何"外部"代理接口,只需要"标准"WebRequest
WebResponse
和ICreateWebRequest
。
如果你不需要访问HttpWebResponse
并且可以处理WebResponse
那就更容易了;我们在单元测试中这样做,以返回"预制"的内容响应以供使用。 我不得不"加倍努力"才能返回实际的HTTP状态代码,以模拟例如404响应,这需要您使用HttpWebResponse
以便您可以访问StatusCode
属性等。
其他解决方案假设一切都HttpWebXXX
忽略WebRequest.Create()
支持的所有内容,HTTP除外,它可以是您想要使用的任何注册前缀的处理程序(通过WebRequest.RegisterPrefix()
如果您忽略这一点,您将错过,因为这是公开其他内容流的好方法,否则您将无法访问, 例如,嵌入的资源流、文件流等。
此外,显式强制将WebRequest.Create()
的返回强制转换为HttpWebRequest
是一条破坏路径,因为方法返回类型是WebRequest
的,并且再次表明对该 API 实际工作方式的一些无知。
虽然有一个公认的答案,但我想分享我的观点。很多时候,修改源代码不是建议的选项。此外,答案至少引入了大量的生产代码。
所以我更喜欢使用免费的 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");
}
我在这篇博文中找到了一个很好的解决方案:
它非常易于使用,您只需要这样做:
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;
}
}