我有一些问题,试图包装我的代码在单元测试中使用。问题在于。我有接口IHttpHandler:
public interface IHttpHandler
{
HttpClient client { get; }
}
使用它的类HttpHandler:
public class HttpHandler : IHttpHandler
{
public HttpClient client
{
get
{
return new HttpClient();
}
}
}
然后是Connection类,它使用simpleIOC注入客户端实现:
public class Connection
{
private IHttpHandler _httpClient;
public Connection(IHttpHandler httpClient)
{
_httpClient = httpClient;
}
}
然后我有一个单元测试项目,它有这个类:
private IHttpHandler _httpClient;
[TestMethod]
public void TestMockConnection()
{
var client = new Connection(_httpClient);
client.doSomething();
// Here I want to somehow create a mock instance of the http client
// Instead of the real one. How Should I approach this?
}
现在很明显,我将在Connection类中拥有从后端检索数据(JSON)的方法。但是,我想为这个类编写单元测试,显然我不想针对真正的后端编写测试,而是一个模拟的后端。我试着给这个问题一个好的答案,但没有成功。我以前可以用Moq来模拟,但从来没有在HttpClient这样的东西上使用过。我应该如何处理这个问题?
不要有一个包装器来创建一个新的HttpClient实例。如果您这样做,您将在运行时耗尽套接字(即使您正在处理HttpClient对象)。
如果使用MOQ,正确的做法是添加使用MOQ . protected;到您的测试,然后编写如下代码:
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent("It worked!")
};
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() => response);
var httpClient = new HttpClient(mockHttpMessageHandler.Object);
你所需要的只是传递给HttpClient ctor的HttpMessageHandler类的测试版本。主要的一点是,您的测试HttpMessageHandler类将有一个HttpRequestHandler委托,调用者可以设置它,并简单地以他们想要的方式处理HttpRequest。
public class FakeHttpMessageHandler : HttpMessageHandler
{
public Func<HttpRequestMessage, CancellationToken, HttpResponseMessage> HttpRequestHandler { get; set; } =
(r, c) =>
new HttpResponseMessage
{
ReasonPhrase = r.RequestUri.AbsoluteUri,
StatusCode = HttpStatusCode.OK
};
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(HttpRequestHandler(request, cancellationToken));
}
}
您可以使用该类的实例来创建具体的HttpClient实例。通过HttpRequestHandler委托,你可以完全控制HttpClient发出的http请求。
我的一个同事注意到,大多数HttpClient方法都在底层调用SendAsync(HttpRequestMessage request, CancellationToken CancellationToken),这是HttpMessageInvoker的一个虚拟方法:
所以到目前为止,模拟HttpClient最简单的方法就是简单地模拟这个特定的方法:
var mockClient = new Mock<HttpClient>();
mockClient.Setup(client => client.SendAsync(It.IsAny<HttpRequestMessage>(), It.IsAny<CancellationToken>())).ReturnsAsync(_mockResponse.Object);
并且您的代码可以调用大多数(但不是全部)HttpClient类方法,包括常规方法
httpClient.SendAsync(req)
点击这里确认
https://github.com/dotnet/corefx/blob/master/src/System.Net.Http/src/System/Net/Http/HttpClient.cs
如果您不介意运行自己的http服务器,可以尝试Xim。其实很简单:
using Xim.Simulators.Api;
[Test]
public async Task TestHttpGetMethod()
{
using var simulation = Simulation.Create();
using var api = simulation
.AddApi()
.AddHandler("GET /books/1234", ApiResponse.Ok())
.Build();
await api.StartAsync();
var httpClient = new HttpClient();
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"{api.Location}/books/1234"));
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.IsTrue(api.ReceivedApiCalls.Any(call => call.Action == "GET /books/1234"));
}
这是使用模拟的一个很好的替代方案,可能适合您在某些场景中的需求。它建立在Kestrel的基础上(是的,我是作者)。