2012-02-01 105 views
19

我在写一些调用Web服务的代码,回读响应并执行一些操作。我的代码如下名义上是这样的:在c#中测试HTTP请求的单元测试#

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)); 
} 

在那里我实际上做任何自定义操作的唯一部件是在ExtractResponseCreateHttpBody方法。然而,单元测试这些方法会让人感觉不对,并希望其他代码能够正确地结合在一起。有什么办法可以拦截HTTP请求并反馈给模拟数据吗?

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

+0

你可以设置一个_(行为不当,或者你想测试的任何东西)_webserver,然后修改'\ Windows \ System32 \ drivers \ etc \ hosts'文件以将请求重定向到该服务器。 – ordag 2012-02-01 12:32:28

+0

相关:https://stackoverflow.com/q/9823039/12484 – 2017-08-30 16:54:59

回答

14

在您的代码中,您不能截取对HttpWebRequest的调用,因为您使用相同的方法创建对象。如果你让另一个对象创建HttpWebRequest,你可以传入一个模拟对象并用它来测试。

因此,不是这样的:

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

使用此:

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

在你的单元测试,你可以在一个WebRequestFactory它创建了一个模拟对象传递。

此外,还可以在一个单独的功能分割你的流阅读代码:

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

这使得可以分别测试ReadStream()

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

+1

在试图用Moles和Moq试图嘲讽HttpWebRequest和HttpWebResponse对象之后,我必须得出结论:它不能完成,使得它成为最有效的方法。 – Ceilingfish 2012-02-01 16:52:47

6

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

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

现在你正试图单元测试类可以使用控制设计模式的反转的实际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)); 
    } 
} 

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

它不再是ClassToTest的责任来处理实际的HTTP请求。它现在是分离的。测试MethodToTest成为一项简单的任务。

而最后一部分显然是有,我们已经介绍了抽象的实现:

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); 
     } 
    } 
} 

然后,您可以配置自己喜欢的DI框架中注入MyDataRetriever实例为ClassToTest类的构造函数在实际应用。

+1

我可以看到你的观点,但是代码所在的类被设计为HTTP数据检索机制。我想测试一下我用来处理请求的代码是否合适,因此如果我抽象出HTTP代码,那么我正在抽象出我想要测试的部分。 – Ceilingfish 2012-02-01 15:28:02

+2

@Ceilingfish,不,你绝对不想测试HttpWebRequest和HttpWebResponse类。这些已经被微软广泛测试过。你想要测试的是你的方法提供了正确的输入给一些黑盒子(它提供给你,并且预计会工作),并且它将它的输出转换为一些预期的格式。这基本上就是你的方法。无论如何,如果没有这种抽象级别,你不能模拟这些类,你不再进行单元测试,而是进行集成测试。 – 2012-02-01 15:40:25

+3

的确,我不想测试这些类,我想测试这些类的_usage_。发送和接收HTTP请求的代码并未完全隐藏,因此它依赖于我的代码正确无误地成功读取请求中的响应。 – Ceilingfish 2012-02-01 16:00:45

4

如果嘲笑HttpWebRequest和HttpWebResponse变得过于繁琐,或者如果您需要在验收测试中测试代码,那么您从“外部”调用您的代码,那么创建假服务可能是最好的方法去。

我实际上编写了一个名为MockHttpServer的开源库来帮助解决这个问题,因此可以非常简单地模拟出任何通过HTTP进行通信的外部服务。

下面是通过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)); 
} 

没有通过所有可用的使用它的选项去GitHub的页面上相当详细自述和库本身是可以通过的NuGet。

+0

我发现https://github.com/unosquare/embedio对于做这件事非常有用 – Ceilingfish 2016-01-20 14:44:04

+0

@eeilingfish这个项目看起来很酷,但似乎只是嘲讽一些回应而已。这是一个完整的Web服务器库,增加了一些复杂性。 – 2016-01-20 15:59:37

2

如果你很乐意移动到HttpClient(一个官方的,可移植的http客户端库),那么我写了一个库,可能有助于称为MockHttp。它提供了一个流畅的API,允许您为使用一系列属性匹配的请求提供响应。