使用.NET Core在集成测试中模拟类型化的HttpClient

5

我希望能在Asp.net core应用程序的集成测试中模拟一个已定义的Typed HttpClient,并寻找一些想法。

假设我有一个服务集合,其中注册了一个已定义的Typed HttpClient,代码如下:

var serviceCollection = new ServiceCollection();

serviceCollection.AddHttpClient<ITestApiClient, TestApiClient>();

var services = serviceCollection.BuildServiceProvider();

还有像这样的TestApiClient定义。

public class TestApiClient: ITestApiClient
{
    HttpClient _httpClient;
    public TestApiClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public void MakeTestApiCall()
    {
        var response = _httpClient.GetAsync("https://reqres.in/api/users?page=2");
        response.Result.Content.ReadAsStringAsync().Result.Dump();
    }
}

我希望找到一种方法,可以使用ServiceCollection将伪造的HttpClient实现注入到TestApiClient中,而不必直接实例化TestApiClient

类似于我们在集成测试中可以进行如下替换真正的Foo实现为伪造的Foo实现。

var fooMock = new Mock<IFoo>();
services.AddSingleton(fooMock);

请注意,我已经找到了一种使用Moq和类似于RichardSzalay.MockHttp的库来模拟HttpClient的方法。我的问题是如何替换ServiceCollection中的类型化Httpclient(是否可以模拟HttpClientFactory?)。

可以看一下控制反转(IoC),例如 https://autofac.org/。 - Daniel W.
@DanielW. 你能详细说明一下吗?Autofac在这方面提供了什么,而Microsoft的DI框架没有呢? - user7368874
这并不是一个好的IoC工具列表:https://www.claudiobernasconi.ch/2019/01/24/the-ultimate-list-of-net-dependency-injection-frameworks/ - Daniel W.
@DanielW。不确定您是否正确理解了我的问题。我知道IoC,仅仅使用不同的IoC框架并不是一个解决方案。如果您能提供更具体的解决方案,我将不胜感激。 - user7368874
@JHBonarius 这只是一些测试代码,展示我想要询问的内容。我同意不应该使用.Result。 是的,我可以将地址更改为本地主机URL,并使用类似WireMock的库来运行模拟HTTP服务器。但是,我想利用微软推荐的WebApplicationFactory模式,保持代码干净和可重用。如果我想要模拟多个HTTP服务,则WireMock代码会变得有点混乱。 - user7368874
显示剩余4条评论
1个回答

1
这是我如何解决在 aspnet core 6 集成测试中模拟 HttpClient 的问题的方法:
我为集成测试创建了一个自定义的 WebApplicationFactory,在其中使用模拟的 HttpMessageHandler 替换 HttpClient 服务,以此来创建一个新的客户端。
internal class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    public Mock<HttpMessageHandler> handlerMock { get; private set; }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Remove existing IHttpClientFactory and HttpClient
            var httpClientFactoryDescriptor = services.SingleOrDefault(d => d.ServiceType ==
                    typeof(IHttpClientFactory));
            services.Remove(httpClientFactoryDescriptor);

            var httpClientDescriptor = services.SingleOrDefault(d => d.ServiceType ==
                    typeof(HttpClient));
            services.Remove(httpClientDescriptor);

            // Create a mock HttpMessageHandler
            this.handlerMock = new Mock<HttpMessageHandler>();

            // Create an HttpClient using the mocked handler
            var client = new HttpClient(this.handlerMock.Object);
            var clientFactoryMock = new Mock<IHttpClientFactory>();
            clientFactoryMock
                .Setup(f => f.CreateClient(It.IsAny<string>()))
                .Returns(client);

            // Add the new HttpClient as singleton
            services.AddSingleton<IHttpClientFactory>(clientFactoryMock.Object);
            services.AddSingleton<HttpClient>(client);
        });
    }
}

模拟的 HttpMessageHandler 作为公共属性暴露,以便测试类可以访问并进行设置:

    [Fact]
    public async Task CallApiTest()
    {
        // Arrange
        var factory = new CustomWebApplicationFactory();
        var client = factory.CreateClient();

        factory.handlerMock
               .Protected()
               .Setup<Task<HttpResponseMessage>>(
                   "SendAsync",
                   ItExpr.Is<HttpRequestMessage>(r => r.Method == HttpMethod.Get),
                   ItExpr.IsAny<CancellationToken>())
               .ReturnsAsync(new HttpResponseMessage
               { 
                   StatusCode = HttpStatusCode.OK,
                   Content = new StringContent("[]") 
               });

        // Act
        var result = await client.GetAsync("/callApi");

        // Assert
        result.StatusCode.Should().Be(HttpStatusCode.OK);

    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接