在c#中对HTTP请求进行unit testing

我正在编写一些调用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)); } 

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

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

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

所以不是这样的:

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

用这个:

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

在unit testing中,您可以传入一个创建模拟对象的WebRequestFactory

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

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

这使得可以单独测试ReadStream()

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

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

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

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

现在,您尝试进行unit testing的类可以使用Inversion of Control设计模式与实际的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)); } } 

处理实际的HTTP请求不再是ClassToTest的责任。 它现在已经解耦了。 测试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类构造函数。

如果嘲弄HttpWebRequest和HttpWebResponse变得太麻烦,或者你需要在validation测试中测试代码,你从“外部”调用代码,那么创建虚假服务可能是最好的方法。

我实际上写了一个名为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获得。

如果您愿意转移到HttpClient (一个官方的,可移植的,http客户端库),那么我会在一段时间之前编写一个可能有助于调用MockHttp的库 。 它提供了一个流畅的API,允许您为使用一系列属性匹配的请求提供响应。