是否可以模拟.NET HttpWebResponse?

Pur*_*ome 63 .net c# unit-testing moq mocking

我有一个集成测试,从第三方服务器获取一些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..
}
Run Code Online (Sandbox Code Playgroud)

当然,这个方法是从我的测试中调用的.

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

任何带有示例代码的建议都会得到最多的评价!

Hac*_*ese 68

您可能希望更改您的使用代码以获取工厂的接口,该工厂创建可以模拟的请求和响应,其中包含实际的实现.

更新:重访

在我的回答被接受之后很久我就得到了投票,我承认我原来的答案质量很差并做了很大的假设.

在4.5+中模拟HttpWebRequest

我原来答案的混淆在于你可以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);
}
Run Code Online (Sandbox Code Playgroud)

更好的答案:摘要响应和请求

这是一个更安全的抽象实现抽象,适用于以前的版本(至少低至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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你不能为HttpWebResponse创建模拟.你会得到这样的错误:"System.ArgumentException:无法实例化类的代理:System.Net.HttpWebResponse.找不到无参数构造函数.参数名称:constructorArguments" (8认同)
  • 我想我打破了投票按钮. (2认同)

Dav*_*rab 5

我会将调用包装在接口后面,并模拟该接口,而不是模拟出HttpWebResponse.

如果您正在测试Web响应是否也访问了我想要的站点,那么这与A类调用WebResponse接口以获取所需数据的测试不同.

对于模拟界面我更喜欢Rhino模拟.请参阅此处了解如何使用它.


Ali*_*tad 5

Microsoft 的 HTTP 堆栈在开发时都没有考虑到单元测试和分离。

您有三个选择:

  • 使对 Web 的调用尽可能小(即发送和取回数据并传递给其他方法)并测试其余部分。就网络调用而言,那里应该会发生很多神奇的事情,而且非常简单。
  • 将 HTTP 调用包装在另一个类中,并在测试时传递您的模拟对象。
  • 换行HttpWebResponse并由HttpWebRequest另外两个类进行。这就是 MVC 团队所做的HttpContext

第二个选项:

interface IWebCaller
{
    string CallWeb(string address);
}
Run Code Online (Sandbox Code Playgroud)