使用 Moq 模拟 HttpClient
Mock HttpClient using Moq
我想对使用 HttpClient
的 class 进行单元测试。我们在 class 构造函数中注入了 HttpClient
对象。
public class ClassA : IClassA
{
private readonly HttpClient _httpClient;
public ClassA(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<HttpResponseMessage> SendRequest(SomeObject someObject)
{
//Do some stuff
var request = new HttpRequestMessage(HttpMethod.Post, "http://some-domain.in");
//Build the request
var response = await _httpClient.SendAsync(request);
return response;
}
}
现在我们要对 ClassA.SendRequest
方法进行单元测试。我们使用 Ms Test
作为单元测试框架,使用 Moq
作为模拟。
当我们尝试模拟 HttpClient
时,它会抛出 NotSupportedException
。
[TestMethod]
public async Task SendRequestAsync_Test()
{
var mockHttpClient = new Mock<HttpClient>();
mockHttpClient.Setup(
m => m.SendAsync(It.IsAny<HttpRequestMessage>()))
.Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
}
我们如何解决这个问题?
那个特定的重载方法不是虚拟的,所以不能被 Moq 覆盖。
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
这就是它抛出 NotSupportedException
的原因
您要找的虚方法就是这个方法
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
然而,模拟 HttpClient
并不像其内部消息处理程序看起来那么简单。
我建议使用带有自定义消息处理程序存根的具体客户端,这将在伪造请求时提供更大的灵活性。
这是委托处理程序存根的示例。
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
请注意,默认构造函数基本上执行您之前尝试模拟的操作。它还允许使用请求委托的更多自定义场景。
有了存根,测试可以重构为
public async Task _SendRequestAsync_Test() {
//Arrange
var handlerStub = new DelegatingHandlerStub();
var client = new HttpClient(handlerStub);
var sut = new ClassA(client);
var obj = new SomeObject() {
//Populate
};
//Act
var response = await sut.SendRequest(obj);
//Assert
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
}
Propper 模拟 HttpClient 是一项艰巨的工作,因为它是在大多数人在 dotnet 中进行单元测试之前编写的。有时我会设置一个存根 HTTP 服务器,该服务器 returns 根据与请求 url 匹配的模式来罐装响应,这意味着您测试真实的 HTTP 请求而不是模拟,而是对本地主机服务器的请求。使用 WireMock.net 使这变得非常简单并且运行速度足以满足我的大部分单元测试需求。
所以不要 http://some-domain.in
在某个端口上使用本地主机服务器设置,然后:
var server = FluentMockServer.Start(/*server and port can be setup here*/);
server.Given(
Request.Create()
.WithPath("/").UsingPost()
)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody("{'attr':'value'}")
);
您可以找到更多详细信息,guidance on using wiremock in tests here.
Moq 可以模拟受保护的方法,例如 HttpMessageHandler 上的 SendAsync,您可以在其构造函数中提供给 HttpClient。
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK
});
var client = new HttpClient(mockHttpMessageHandler.Object);
复制自 https://thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/
最近要模拟HttpClient,用了Moq.Contrib.HttpClient。这是我需要的,而且使用简单,所以我想我应该把它扔掉。
这是一个一般用法的例子:
// All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();
// A simple example that returns 404 for any request
handler.SetupAnyRequest()
.ReturnsResponse(HttpStatusCode.NotFound);
// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
.ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");
// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
.ReturnsResponse(bytes, configure: response =>
{
response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
})
.Verifiable(); // Naturally we can use Moq methods as well
// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));
有关详细信息,请查看作者的博客 post here。
我想对使用 HttpClient
的 class 进行单元测试。我们在 class 构造函数中注入了 HttpClient
对象。
public class ClassA : IClassA
{
private readonly HttpClient _httpClient;
public ClassA(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<HttpResponseMessage> SendRequest(SomeObject someObject)
{
//Do some stuff
var request = new HttpRequestMessage(HttpMethod.Post, "http://some-domain.in");
//Build the request
var response = await _httpClient.SendAsync(request);
return response;
}
}
现在我们要对 ClassA.SendRequest
方法进行单元测试。我们使用 Ms Test
作为单元测试框架,使用 Moq
作为模拟。
当我们尝试模拟 HttpClient
时,它会抛出 NotSupportedException
。
[TestMethod]
public async Task SendRequestAsync_Test()
{
var mockHttpClient = new Mock<HttpClient>();
mockHttpClient.Setup(
m => m.SendAsync(It.IsAny<HttpRequestMessage>()))
.Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
}
我们如何解决这个问题?
那个特定的重载方法不是虚拟的,所以不能被 Moq 覆盖。
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
这就是它抛出 NotSupportedException
您要找的虚方法就是这个方法
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
然而,模拟 HttpClient
并不像其内部消息处理程序看起来那么简单。
我建议使用带有自定义消息处理程序存根的具体客户端,这将在伪造请求时提供更大的灵活性。
这是委托处理程序存根的示例。
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
请注意,默认构造函数基本上执行您之前尝试模拟的操作。它还允许使用请求委托的更多自定义场景。
有了存根,测试可以重构为
public async Task _SendRequestAsync_Test() {
//Arrange
var handlerStub = new DelegatingHandlerStub();
var client = new HttpClient(handlerStub);
var sut = new ClassA(client);
var obj = new SomeObject() {
//Populate
};
//Act
var response = await sut.SendRequest(obj);
//Assert
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
}
Propper 模拟 HttpClient 是一项艰巨的工作,因为它是在大多数人在 dotnet 中进行单元测试之前编写的。有时我会设置一个存根 HTTP 服务器,该服务器 returns 根据与请求 url 匹配的模式来罐装响应,这意味着您测试真实的 HTTP 请求而不是模拟,而是对本地主机服务器的请求。使用 WireMock.net 使这变得非常简单并且运行速度足以满足我的大部分单元测试需求。
所以不要 http://some-domain.in
在某个端口上使用本地主机服务器设置,然后:
var server = FluentMockServer.Start(/*server and port can be setup here*/);
server.Given(
Request.Create()
.WithPath("/").UsingPost()
)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody("{'attr':'value'}")
);
您可以找到更多详细信息,guidance on using wiremock in tests here.
Moq 可以模拟受保护的方法,例如 HttpMessageHandler 上的 SendAsync,您可以在其构造函数中提供给 HttpClient。
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK
});
var client = new HttpClient(mockHttpMessageHandler.Object);
复制自 https://thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/
最近要模拟HttpClient,用了Moq.Contrib.HttpClient。这是我需要的,而且使用简单,所以我想我应该把它扔掉。
这是一个一般用法的例子:
// All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();
// A simple example that returns 404 for any request
handler.SetupAnyRequest()
.ReturnsResponse(HttpStatusCode.NotFound);
// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
.ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");
// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
.ReturnsResponse(bytes, configure: response =>
{
response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
})
.Verifiable(); // Naturally we can use Moq methods as well
// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));
有关详细信息,请查看作者的博客 post here。