问题
I am writing a simple unit test for this small service that simply calls external APIs:
public class ApiCaller : IApiCaller
{
private readonly IHttpClientFactory _httpFactory;
public ApiCaller(IHttpClientFactory httpFactory)
{
_httpFactory = httpFactory;
}
public async Task<T> GetResponseAsync<T>(Uri url)
{
using (HttpClient client = _httpFactory.CreateClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromSeconds(20);
using (HttpResponseMessage response = await client.GetAsync(url))
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(responseBody);
}
}
}
}
My first question is: it doesn't seem to be very common practice mocking and therefore testing such services and I am wondering if there is some specific explanation.
Second, I tried to write a simple unit test but I cannot Mock the GetAsync call since HttpClient doesn't implement any interface.
public class ApiCallerTest
{
private readonly ApiCaller _target;
private readonly Mock<IHttpClientFactory> _httpClientFactory;
public ApiCallerTest()
{
_httpClientFactory = new Mock<IHttpClientFactory>();
_target = new ApiCaller(_httpClientFactory.Object);
}
[Fact]
public void WhenACorrectUrlIsProvided_ServiceShouldReturn()
{
var client = new HttpClient();
_httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(client);
var httpMessageHandler = new Mock<HttpMessageHandler>();
}
}
回答1:
The code below is what you should use regardless of the method in the HttpClient class you use (GetAsync, PostAsync, etc.). All these methods are created for the convenience of the programmer. What they do is use the SendAsync method of the HttpMessageHandler class.
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
// Setup Protected method on HttpMessageHandler mock.
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
{
HttpResponseMessage response = new HttpResponseMessage();
// configure your response here
return response;
});
And then you use it this way:
var httpClient = new HttpClient(mockHttpMessageHandler.Object);
var result = await httpClient.GetAsync(url, cancellationToken);
You can also take a look here How to create mock for httpclient getasync method?
回答2:
Setup your Mock HttpMessageHandler
first and pass it to the constructor of your HttpClient
. Then you can setup a Mock for the GetAsync
method on the handler like this:
var httpMessageHandler = new Mock<HttpMessageHandler>();
// Setup Protected method on HttpMessageHandler mock.
httpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>(
"GetAsync",
ItExpr.IsAny<string>(),
ItExpr.IsAny<CancellationToken>()
)
.ReturnsAsync((HttpRequestMessage request, CancellationToken token) =>
{
HttpResponseMessage response = new HttpResponseMessage();
// Setup your response for testing here.
return response;
});
var client = new HttpClient(httpMessageHandler.Object);
This is modified from a unit test I use to mockSendAsync
, but it should be very similar.
来源:https://stackoverflow.com/questions/56064031/mocking-httpclient-getasync-by-using-moq-library-in-xunit-test