使用 C# 对 HTTP 请求进行单元测试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9095627/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 05:53:56  来源:igfitidea点击:

Unit testing HTTP requests in c#

c#.netunit-testing

提问by Ceilingfish

I'm writing some code that calls a web service, reads back the response and does something with it. My code looks nominally like this:

我正在编写一些调用 Web 服务、读回响应并对其进行处理的代码。我的代码名义上是这样的:

string body = CreateHttpBody(regularExpression, strategy);

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url);
request.Method = "POST";
request.ContentType = "text/plain; charset=utf-8";

using (Stream requestStream = request.GetRequestStream())
{
    requestStream.Write(Encoding.UTF8.GetBytes(body), 0, body.Length);
    requestStream.Flush();
}

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
    byte[] data = new byte[response.ContentLength];

    using (Stream stream = response.GetResponseStream())
    {
        int bytesRead = 0;

        while (bytesRead < data.Length)
        {
            bytesRead += stream.Read(data, bytesRead, data.Length - bytesRead);
        }
    }

    return ExtractResponse(Encoding.UTF8.GetString(data));
}

The only parts where I am actually doing any custom manipulation is in the ExtractResponseand CreateHttpBodymethods. However it feels wrong to just unit test those methods, and hope that the rest of the code comes together correctly. Is there any way I can intercept the HTTP request and feed it mock data instead?

我实际进行任何自定义操作的唯一部分是在ExtractResponseCreateHttpBody方法中。然而,仅仅对这些方法进行单元测试感觉是错误的,并希望其余的代码正确地组合在一起。有什么办法可以拦截 HTTP 请求并将其提供给模拟数据吗?

EDITThis information is now out of date. It is much easier to construct this kind of code using the System.Net.Http.HttpClientlibraries.

编辑此信息现已过时。使用System.Net.Http.HttpClient库构建这种代码要容易得多。

采纳答案by Sjoerd

In your code you can not intercept the calls to HttpWebRequestbecause you create the object in the same method. If you let another object create the HttpWebRequest, you can pass in a mock object and use that to test.

在您的代码中,您无法拦截对的调用,HttpWebRequest因为您在同一方法中创建了对象。如果让另一个对象创建HttpWebRequest,则可以传入一个模拟对象并使用它进行测试。

So instead of this:

所以而不是这个:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_url);

Use this:

用这个:

IHttpWebRequest request = this.WebRequestFactory.Create(_url);

In your unit test, you can pass in a WebRequestFactorywhich creates a mock object.

在您的单元测试中,您可以传入WebRequestFactory创建模拟对象的 a。

Furthermore, you can split of your stream reading code in a separate function:

此外,您可以在单独的函数中拆分流读取代码:

using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
    byte[] data = ReadStream(response.GetResponseStream());
    return ExtractResponse(Encoding.UTF8.GetString(data));
}

This makes it possible to test ReadStream()separately.

这使得ReadStream()单独测试成为可能。

To do more of an integration test, you can set up your own HTTP server which returns test data, and pass the URL of that server to your method.

要进行更多的集成测试,您可以设置自己的 HTTP 服务器来返回测试数据,并将该服务器的 URL 传递给您的方法。

回答by Darin Dimitrov

I would probably start by refactoring the code in order to make it more weakly coupled to an actual HTTP request. Right now this code seems to do quite a lot of things.

我可能会从重构代码开始,以使其与实际 HTTP 请求的耦合更弱。现在这段代码似乎做了很多事情。

This could be done by introducing an abstraction:

这可以通过引入一个抽象来完成:

public interface IDataRetriever
{
    public byte[] RetrieveData(byte[] request);
}

Now the class that you are trying to unit test could be decoupled from the actual HTTP request using the Inversion of Control design pattern:

现在,您尝试进行单元测试的类可以使用控制反转设计模式与实际的 HTTP 请求分离:

public class ClassToTest
{
    private readonly IDataRetriever _dataRetriever;
    public Foo(IDataRetriever dataRetriever)
    {
        _dataRetriever = dataRetriever;
    }

    public string MethodToTest(string regularExpression, string strategy)
    {
        string body = CreateHttpBody(regularExpression, strategy);
        byte[] result = _dataRetriever.RetrieveData(Encoding.UTF8.GetBytes(body));
        return ExtractResponse(Encoding.UTF8.GetString(result));
    }
}

It is no longer the ClassToTest's responsibility to deal with an actual HTTP request. It is now decoupled. Testing the MethodToTestbecomes a trivial task.

处理实际的 HTTP 请求不再是 ClassToTest 的责任。现在已经解耦了。测试MethodToTest成为一项微不足道的任务。

And the last part obviously is to have an implementation of the abstraction that we have introduced:

最后一部分显然是我们引入的抽象的实现:

public class MyDataRetriever : IDataRetriever
{
    private readonly string _url;
    public MyDataRetriever(string url)
    {
        _url = url;
    }

    public byte[] RetrieveData(byte[] request)
    {
        using (var client = new WebClient())
        {
            client.Headers[HttpRequestHeader.ContentType] = "text/plain; charset=utf-8";
            return client.UploadData(_url, request);
        }
    }
}

You could then configure your favorite DI framework to inject a MyDataRetrieverinstance into the ClassToTestclass constructor in your actual application.

然后,您可以配置您最喜欢的 DI 框架,以将MyDataRetriever实例注入ClassToTest实际应用程序中的类构造函数。

回答by Jeffrey Harmon

If mocking out the HttpWebRequest and HttpWebResponse becomes too cumbersome, or if you ever need to test code in an acceptance test, where you are calling your code from the "outside", then creating a fake service is probably the best way to go.

如果模拟 HttpWebRequest 和 HttpWebResponse 变得太麻烦,或者如果您需要在验收测试中测试代码,从“外部”调用您的代码,那么创建一个假服务可能是最好的方法。

I actually wrote an open source library called MockHttpServerto assist with this, making it super simple to mock out any external services that communicate over HTTP.

我实际上编写了一个名为MockHttpServer 的开源库来帮助解决这个问题,这使得模拟任何通过 HTTP 通信的外部服务变得非常简单。

Here is an example of using it with RestSharp to call an API endpoint with it, but HttpWebRequest would work just as well.

这是将它与 RestSharp 一起使用以调用 API 端点的示例,但 HttpWebRequest 也能正常工作。

using (new MockServer(3333, "/api/customer", (req, rsp, prm) => "Result Body"))
{
    var client = new RestClient("http://localhost:3333/");
    var result = client.Execute(new RestRequest("/api/customer", Method.GET));
}

There is a fairly detailed readme on the GitHub page that goes through all the options available for using it, and the library itself is available through NuGet.

GitHub 页面上有一个相当详细的自述文件,其中介绍了所有可用的选项,库本身可通过 NuGet 获得。

回答by Richard Szalay

If you're happy to move to HttpClient(an official, portable, http client library), then I wrote a library a while back that may help called MockHttp. It provides a fluent API that allows you provide responses for requests matched using a range of attributes.

如果您愿意迁移到HttpClient(一个官方的、可移植的、http 客户端库),那么我不久前编写了一个名为MockHttp 的库。它提供了一个流畅的 API,允许您为使用一系列属性匹配的请求提供响应。