我有一些问题,试图包装我的代码在单元测试中使用。问题在于。我有接口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这样的东西上使用过。我应该如何处理这个问题?
这里有一个简单的解决方法,对我来说很有效。
使用moq mock库。
// ARRANGE
var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict);
handlerMock
.Protected()
// Setup the PROTECTED method to mock
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
// prepare the expected response of the mocked http call
.ReturnsAsync(new HttpResponseMessage()
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("[{'id':1,'value':'1'}]"),
})
.Verifiable();
// use real http client with mocked handler here
var httpClient = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://test.com/"),
};
var subjectUnderTest = new MyTestClass(httpClient);
// ACT
var result = await subjectUnderTest
.GetSomethingRemoteAsync('api/test/whatever');
// ASSERT
result.Should().NotBeNull(); // this is fluent assertions here...
result.Id.Should().Be(1);
// also check the 'http' call was like we expected it
var expectedUri = new Uri("http://test.com/api/test/whatever");
handlerMock.Protected().Verify(
"SendAsync",
Times.Exactly(1), // we expected a single external request
ItExpr.Is<HttpRequestMessage>(req =>
req.Method == HttpMethod.Get // we expected a GET request
&& req.RequestUri == expectedUri // to this uri
),
ItExpr.IsAny<CancellationToken>()
);
来源:https://gingter.org/2018/07/26/how-to-mock-httpclient-in-your-net-c-unit-tests/
加入这个派对有点晚了,但我喜欢在带有下游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);
也许在您当前的项目中会有一些代码需要更改,但对于新项目,您绝对应该考虑使用Flurl。
https://flurl.dev
它是一个。net的HTTP客户端库,具有一个流畅的接口,特别支持使用它来发出HTTP请求的代码的可测试性。
网站上有很多代码示例,但简单地说,你在代码中是这样使用的。
添加用途。
using Flurl;
using Flurl.Http;
发送get请求并读取响应。
public async Task SendGetRequest()
{
var response = await "https://example.com".GetAsync();
// ...
}
在单元测试中,Flurl充当一个模拟,可以将其配置为所需的行为,还可以验证所执行的调用。
using (var httpTest = new HttpTest())
{
// Arrange
httpTest.RespondWith("OK", 200);
// Act
await sut.SendGetRequest();
// Assert
httpTest.ShouldHaveCalled("https://example.com")
.WithVerb(HttpMethod.Get);
}
微软现在提供了使用IHttpClientFactory而不是直接使用HttpClient的替代方案:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-5.0
使用返回预期结果的请求进行模拟:
private LoginController GetLoginController()
{
var expected = "Hello world";
var mockFactory = new Mock<IHttpClientFactory>();
var mockMessageHandler = new Mock<HttpMessageHandler>();
mockMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(expected)
});
var httpClient = new HttpClient(mockMessageHandler.Object);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
var logger = Mock.Of<ILogger<LoginController>>();
var controller = new LoginController(logger, mockFactory.Object);
return controller;
}
来源:
https://stackoverflow.com/a/66256132/3850405
HttpClient的可扩展性在于传递给构造函数的HttpMessageHandler。它的目的是允许特定于平台的实现,但您也可以模拟它。不需要为HttpClient创建装饰器包装。
如果你更喜欢DSL而不是使用Moq,我在GitHub/Nuget上有一个库,它可以让事情变得更容易一些:https://github.com/richardszalay/mockhttp
Nuget包RichardSzalay。MockHttp可以在这里找到。
var mockHttp = new MockHttpMessageHandler();
// Setup a respond for the user api (including a wildcard in the URL)
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}"); // Respond with JSON
// Inject the handler or client into your application code
var client = new HttpClient(mockHttp);
var response = await client.GetAsync("http://localhost/api/user/1234");
// or without async: var response = client.GetAsync("http://localhost/api/user/1234").Result;
var json = await response.Content.ReadAsStringAsync();
// No network connection required
Console.Write(json); // {'name' : 'Test McGee'}
不要有一个包装器来创建一个新的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);