我有一些问题,试图包装我的代码在单元测试中使用。问题在于。我有接口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这样的东西上使用过。我应该如何处理这个问题?
这是一个老问题,但我有一种冲动,想用一个我在这里没有看到的解决方案来扩展答案。
您可以伪造Microsoft程序集(System.Net.Http),然后在测试期间使用ShinsContext。
In VS 2017, right click on the System.Net.Http assembly and choose "Add Fakes Assembly"
Put your code in the unit test method under a ShimsContext.Create() using. This way, you can isolate the code where you are planning to fake the HttpClient.
Depends on your implementation and test, I would suggest to implement all the desired acting where you call a method on the HttpClient and want to fake the returned value. Using ShimHttpClient.AllInstances will fake your implementation in all the instances created during your test. For example, if you want to fake the GetAsync() method, do the following:
[TestMethod]
public void FakeHttpClient()
{
using (ShimsContext.Create())
{
System.Net.Http.Fakes.ShimHttpClient.AllInstances.GetAsyncString = (c, requestUri) =>
{
//Return a service unavailable response
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
var task = Task.FromResult(httpResponseMessage);
return task;
};
//your implementation will use the fake method(s) automatically
var client = new Connection(_httpClient);
client.doSomething();
}
}
加入这个派对有点晚了,但我喜欢在带有下游REST依赖的dotnet核心微服务的集成测试中尽可能使用wiremocking (https://github.com/WireMock-Net/WireMock.Net)。
通过实现一个扩展IHttpClientFactory的TestHttpClientFactory,我们可以重写这个方法
HttpClient CreateClient(字符串名称)
所以当你在应用中使用命名客户端时,你可以控制返回一个连接到你的wiremock的HttpClient。
这种方法的好处是,您不会更改正在测试的应用程序中的任何内容,并且允许课程集成测试对您的服务执行实际的REST请求,并模拟实际下游请求应该返回的json(或任何东西)。这将导致在应用程序中进行简洁的测试和尽可能少的模拟。
public class TestHttpClientFactory : IHttpClientFactory
{
public HttpClient CreateClient(string name)
{
var httpClient = new HttpClient
{
BaseAddress = new Uri(G.Config.Get<string>($"App:Endpoints:{name}"))
// G.Config is our singleton config access, so the endpoint
// to the running wiremock is used in the test
};
return httpClient;
}
}
and
// in bootstrap of your Microservice
IHttpClientFactory factory = new TestHttpClientFactory();
container.Register<IHttpClientFactory>(factory);
这是一个老问题,但我有一种冲动,想用一个我在这里没有看到的解决方案来扩展答案。
您可以伪造Microsoft程序集(System.Net.Http),然后在测试期间使用ShinsContext。
In VS 2017, right click on the System.Net.Http assembly and choose "Add Fakes Assembly"
Put your code in the unit test method under a ShimsContext.Create() using. This way, you can isolate the code where you are planning to fake the HttpClient.
Depends on your implementation and test, I would suggest to implement all the desired acting where you call a method on the HttpClient and want to fake the returned value. Using ShimHttpClient.AllInstances will fake your implementation in all the instances created during your test. For example, if you want to fake the GetAsync() method, do the following:
[TestMethod]
public void FakeHttpClient()
{
using (ShimsContext.Create())
{
System.Net.Http.Fakes.ShimHttpClient.AllInstances.GetAsyncString = (c, requestUri) =>
{
//Return a service unavailable response
var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
var task = Task.FromResult(httpResponseMessage);
return task;
};
//your implementation will use the fake method(s) automatically
var client = new Connection(_httpClient);
client.doSomething();
}
}
如果您不介意运行自己的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的基础上(是的,我是作者)。
不要有一个包装器来创建一个新的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);