How to mock the new HttpClientFactory in .NET Core 2.1 using Moq

梦想的初衷 提交于 2021-01-29 10:00:44

问题


.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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!