为集成测试创建默认的HttpClientFactory

26

我有一个已经编写好的客户端,我想将其注册为单例:

public class SomeHttpClient
{
    private readonly IHttpClientFactory _clientFactory;

    public SomeHttpClient(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public Task MakeSomeCallAsync(...)
    {
        var client = _clientFactory.Create();
        // Do more stuff
    }
}

这个类型化客户端必须是一个单例,因为它包含一些断路器逻辑,如果API对客户端进行了速率限制,则会打开断路器。在整个应用程序范围内必须打开断路器,而不仅仅是当前请求管道(否则客户端将继续命中已经被其他实例限制速率的API)。这已经通过使用一个封装了该功能的类型化客户端来实现(不是Polly)。

现在,由于类型化客户端将成为单例,我们不能注入一个单一的HttpClient,因为这将带来我们之前在使用单个长时间生存的HttpClient时遇到的所有问题。显然的解决方案是注入一个HttpClientFactory,现在可以用于每次类型化客户端需要一个HttpClient实例时发出请求,而无需担心生命周期管理等问题,因为这已经延迟到HttpClientFactory中。

我的问题是如何为一个简单的函数集成测试创建一个默认的HttpClientFactory,我想在自动化测试中实例化类型化客户端并查看是否可以访问API并获得成功响应?

我不想设置一个ASP.NET Core TestServer和整个应用程序,因为这对我想要进行的功能测试来说是过度的。

没有公共构造函数可以用于注入HttpClientFactory吗?


为什么不能定义和使用自己的httpclient工厂接口? - jgauffin
3个回答

22

这是一个实现,每次调用将返回一个新的HttpClient实例(与接口合同规定的一样),但会重复使用相同的HttpMessageHandler(类似于真正的HttpClientFactory):

public sealed class DefaultHttpClientFactory : IHttpClientFactory, IDisposable
{
    private readonly Lazy<HttpMessageHandler> _handlerLazy = new (() => new HttpClientHandler());

    public HttpClient CreateClient(string name) => new (_handlerLazy.Value, disposeHandler: false);

    public void Dispose()
    {
        if (_handlerLazy.IsValueCreated)
        {
            _handlerLazy.Value.Dispose();
        }
    }
}


对于单例,您需要将DefaultHttpClientFactory默认构造函数明确设置为私有。 - Steven J
这是我所说的,这不是单例模式 :) 你可以放置私有构造函数并设置CreateClient为静态,或者如果你更喜欢像你那样使用虚方法,那么就不需要设置静态的懒惰实例并管理DefaultHttpClientFactory对象的生命周期,然后将其放在一个对象容器中。 - Steven J
嘿,我所说的使用对象容器通常是进行依赖注入。 - Steven J

13
现在我的问题是,我如何为一个简单的功能集成测试创建一个默认的HttpClientFactory,在该测试中,我想实例化一个类型化的客户端,并查看是否可以从自动化测试中获得成功响应? 您的测试可以采用您在StartUp中用于配置HttpClientFactory的确切代码(这是使用Polly策略的示例,但它可以是HttpClientFactory上的任何配置)。
IServiceCollection services = new ServiceCollection(); // [1]

services.AddHttpClient(TestClient)
    .AddPolicyHandler(HttpPolicyExtensions.HandleTransientHttpError()
    .RetryAsync(3); // [2]

第2行可能是你的StartUp类的确切代码。你可以将其从正常的ConfigureServices(...)方法中分解出来,放入一个单独的static方法中,这样你的测试也可以调用它——因此你的测试可以测试StartUp所配置的内容。

然后在测试中只需:

IHttpClientFactory factory = services
    .BuildServiceProvider()
    .GetRequiredService<IHttpClientFactory>();

var sut = new SomeHttpClient(factory);

6

我刚刚为了测试自己创建了一个(我原以为可能已经有一种方法可以帮我做到这一点):

type DefaultHttpClientFactory() =
    interface IHttpClientFactory with
        member __.CreateClient (name) =
            new HttpClient()

3
加油,F# 真是个作弊的语言啊!与 C# 相比,它的冗长简直不值一提 =) - Pavel Voronin
1
对我来说,这是一个很好的答案(如果你转换回C#),因为我实际上想要在我的集成测试中发送数据。使用这种方法,你可以完全控制。非常好! - ghostJago
1
有人能把那段代码转换回C#吗? - Vida
字面上就是:public class DefaultHttpClientFactory : IHttpClientFactory { public HttpClient CreateClient(string name) { return new HttpClient(); } } - Lee Benson
字面上就是:public class DefaultHttpClientFactory : IHttpClientFactory { public HttpClient CreateClient(string name) { return new HttpClient(); } } - undefined

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