问题
.NET Core 2.1 comes with this new factory called HttpClientFactory
, but I can't figure out how to mock it to unit test some methods that include REST service calls.
The factory is being injected using .NET Core IoC container, and what the method does is create a new client from the factory:
var client = _httpClientFactory.CreateClient();
And then using the client to get data from a REST service:
var result = await client.GetStringAsync(url);
回答1:
The HttpClientFactory
is derived from IHttpClientFactory Interface So it is just a matter of creating a mock of the interface
var mockFactory = new Mock<IHttpClientFactory>();
Depending on what you need the client for, you would then need to setup the mock to return a HttpClient
for the test.
This however requires an actual HttpClient
.
var clientHandlerStub = new DelegatingHandlerStub();
var client = new HttpClient(clientHandlerStub);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
IHttpClientFactory factory = mockFactory.Object;
The factory can then be injected into the dependent system under test when exercising the test.
If you do not want the client calling actual endpoints then you will need to create a fake delegate handler to intercept the requests.
Example of the handler stub used to fake the requests
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);
}
}
Taken from an answer I gave here
Reference Mock HttpClient using Moq
Suppose you have a controller
[Route("api/[controller]")]
public class ValuesController : Controller {
private readonly IHttpClientFactory _httpClientFactory;
public ValuesController(IHttpClientFactory httpClientFactory) {
_httpClientFactory = httpClientFactory;
}
[HttpGet]
public async Task<IActionResult> Get() {
var client = _httpClientFactory.CreateClient();
var url = "http://example.com";
var result = await client.GetStringAsync(url);
return Ok(result);
}
}
and wanted to test the Get()
action.
public async Task Should_Return_Ok() {
//Arrange
var expected = "Hello World";
var mockFactory = new Mock<IHttpClientFactory>();
var configuration = new HttpConfiguration();
var clientHandlerStub = new DelegatingHandlerStub((request, cancellationToken) => {
request.SetConfiguration(configuration);
var response = request.CreateResponse(HttpStatusCode.OK, expected);
return Task.FromResult(response);
});
var client = new HttpClient(clientHandlerStub);
mockFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(client);
IHttpClientFactory factory = mockFactory.Object;
var controller = new ValuesController(factory);
//Act
var result = await controller.Get();
//Assert
result.Should().NotBeNull();
var okResult = result as OkObjectResult;
var actual = (string) okResult.Value;
actual.Should().Be(expected);
}
回答2:
In addition to the previous post that describes how to setup a stub, you can just use Moq to setup the DelegatingHandler
:
var clientHandlerMock = new Mock<DelegatingHandler>();
clientHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK))
.Verifiable();
clientHandlerMock.As<IDisposable>().Setup(s => s.Dispose());
var httpClient = new HttpClient(clientHandlerMock.Object);
var clientFactoryMock = new Mock<IHttpClientFactory>(MockBehavior.Strict);
clientFactoryMock.Setup(cf => cf.CreateClient()).Returns(httpClient).Verifiable();
clientFactoryMock.Verify(cf => cf.CreateClient());
clientHandlerMock.Protected().Verify("SendAsync", Times.Exactly(1), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
回答3:
This code threw this exception for me, System.InvalidOperationException: The request does not have an associated configuration object or the provided configuration was null.
So included this in the test method, and it works.
var configuration = new HttpConfiguration();
var request = new HttpRequestMessage();
request.SetConfiguration(configuration);
来源:https://stackoverflow.com/questions/61750427/how-do-i-unit-test-a-repo-that-calls-ihttpclientfactory-with-nunit