模拟 HttpClient SendAsync 方法
mock HttpClient SendAsync method
我的代码使用 HttpClient 来检索一些数据
HttpClient client = new HttpClient
{
BaseAddress = new Uri("myurl.com"),
};
var msg = new HttpRequestMessage(HttpMethod.Get, "myendpoint");
var res = await client.SendAsync(msg);
如何在 HttpClient 上模拟此 SendAsync 方法并将其注入到 .net 核心 ServiceCollection 中?
我试过这样模拟
var mockFactory = new Mock<IHttpClientFactory>();
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{'name':thecodebuzz,'city':'USA'}"),
});
var client = new HttpClient(mockHttpMessageHandler.Object);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
but how to inject this mockFactory into ServiceCollection? Or maybe there is some easier or different way around?
如果你真的需要模拟 HttpClient 本身,看看这个库:
https://github.com/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 = mockHttp.ToHttpClient();
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'}
与其模拟 HTTP 调用,不如封装它?然后你可以模拟 encapsulation/abstraction.
例如:
interface IClient
{
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
}
class HttpClientAdapter : IClient
{
readonly HttpClient _client;
public HttpClientAdapter(HttpClient client)
{
_client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) => _client.SendAsync(request, cancellationToken);
}
使您的代码依赖于 IClient
接口。在正常使用期间,您将在 HttpClientAdapter
实现中使用真正的 HttpClient
。对于测试,您可以模拟 IClient
.
请注意,使抽象级别比这高一点可能对您更有用。例如,如果您希望将来自 HTTP 响应的 JSON 字符串解析为一些 DataObject
,那么您的 IClient
界面可能看起来更像这样:
class DataObject
{
public string Name { get; set; }
public string City { get; set; }
}
interface IClient
{
Task<DataObject> GetAsync(CancellationToken cancellationToken = default);
}
public class ClientImplementation : IClient
{
readonly HttpClient _client;
public ClientImplementation(HttpClient client)
{
_client = client;
}
public async Task<DataObject> GetAsync(CancellationToken cancellationToken = default)
{
var response = await _client.SendAsync(...);
var dataObject = new DataObject();
// parse the response into the data object
return dataObject;
}
}
在这里画线的好处是您的测试工作量会减少。例如,您的模拟代码不必设置 HttpResponseMessage
个对象。
您选择在哪里为您的抽象划定界限完全取决于您。但关键要点是:一旦您的代码依赖于一个小接口,那么模拟该接口并测试您的代码就很容易了。
我的代码使用 HttpClient 来检索一些数据
HttpClient client = new HttpClient
{
BaseAddress = new Uri("myurl.com"),
};
var msg = new HttpRequestMessage(HttpMethod.Get, "myendpoint");
var res = await client.SendAsync(msg);
如何在 HttpClient 上模拟此 SendAsync 方法并将其注入到 .net 核心 ServiceCollection 中?
我试过这样模拟
var mockFactory = new Mock<IHttpClientFactory>();
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{'name':thecodebuzz,'city':'USA'}"),
});
var client = new HttpClient(mockHttpMessageHandler.Object);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
but how to inject this mockFactory into ServiceCollection? Or maybe there is some easier or different way around?
如果你真的需要模拟 HttpClient 本身,看看这个库: https://github.com/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 = mockHttp.ToHttpClient();
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'}
与其模拟 HTTP 调用,不如封装它?然后你可以模拟 encapsulation/abstraction.
例如:
interface IClient
{
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
}
class HttpClientAdapter : IClient
{
readonly HttpClient _client;
public HttpClientAdapter(HttpClient client)
{
_client = client;
}
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) => _client.SendAsync(request, cancellationToken);
}
使您的代码依赖于 IClient
接口。在正常使用期间,您将在 HttpClientAdapter
实现中使用真正的 HttpClient
。对于测试,您可以模拟 IClient
.
请注意,使抽象级别比这高一点可能对您更有用。例如,如果您希望将来自 HTTP 响应的 JSON 字符串解析为一些 DataObject
,那么您的 IClient
界面可能看起来更像这样:
class DataObject
{
public string Name { get; set; }
public string City { get; set; }
}
interface IClient
{
Task<DataObject> GetAsync(CancellationToken cancellationToken = default);
}
public class ClientImplementation : IClient
{
readonly HttpClient _client;
public ClientImplementation(HttpClient client)
{
_client = client;
}
public async Task<DataObject> GetAsync(CancellationToken cancellationToken = default)
{
var response = await _client.SendAsync(...);
var dataObject = new DataObject();
// parse the response into the data object
return dataObject;
}
}
在这里画线的好处是您的测试工作量会减少。例如,您的模拟代码不必设置 HttpResponseMessage
个对象。
您选择在哪里为您的抽象划定界限完全取决于您。但关键要点是:一旦您的代码依赖于一个小接口,那么模拟该接口并测试您的代码就很容易了。