当前位置: 首页 > 知识库问答 >
问题:

如何使用自定义HttpClientHandler配置对HttpClient进行单元测试/依赖注入类

游安康
2023-03-14

我正在寻找有关如何改进当前测试类(以下示例)的设计的建议,该类依赖于具有自定义HttpClient配置的HttpClientHandler。我通常使用构造函数注入来注入一个在整个应用程序中一致的HttpClient,但是,因为这是在类库中,所以我不能依赖库的使用者来正确设置HttpClientHandler

    null

这个设置对我来说似乎相当复杂,我希望有人能提出一个改进,但我知道这可能是相当主观的,所以如果有任何既定的模式或设计规则要遵循在这种情况下,我会非常感谢听到他们。

我包含了DownloadSomethingAsync()方法,只是为了演示为什么HttpClientHandler需要非标准配置。默认情况下,重定向响应在内部自动重定向而不返回响应,我需要重定向响应,这样我就可以将它包装在一个报告下载进度的类中(其功能与此问题无关)。

public class DemoClass
{
    private static readonly HttpClient defaultHttpClient = new HttpClient(
            new HttpClientHandler
            {
                AllowAutoRedirect = false
            });

    private readonly ILogger<DemoClass> logger;
    private readonly HttpClient httpClient;

    public DemoClass(ILogger<DemoClass> logger) : this(logger, defaultHttpClient) { }

    private DemoClass(ILogger<DemoClass> logger, HttpClient httpClient)
    {
        this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
        this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
    }

    [Obsolete("This is only provided for testing and should not be used in calling code")]
    internal static DemoClass CreateWithCustomHttpClient(ILogger<DemoClass> logger, HttpClient httpClient)
        => new DemoClass(logger, httpClient);

    public async Task<FileSystemInfo> DownloadSomethingAsync(CancellationToken ct = default)
    {
        // Build the request
        logger.LogInformation("Sending request for download");
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://example.com/downloadredirect");

        // Send the request
        HttpResponseMessage response = await httpClient.SendAsync(request, ct);

        // Analyse the result
        switch (response.StatusCode)
        {
            case HttpStatusCode.Redirect:
                break;
            case HttpStatusCode.NoContent:
                return null;
            default: throw new InvalidOperationException();
        }

        // Get the redirect location
        Uri redirect = response.Headers.Location;

        if (redirect == null)
            throw new InvalidOperationException("Redirect response did not contain a redirect URI");

        // Create a class to handle the download with progress tracking
        logger.LogDebug("Wrapping release download request");
        IDownloadController controller = new HttpDownloadController(redirect);

        // Begin the download
        logger.LogDebug("Beginning release download");

        return await controller.DownloadAsync();
    }
}

共有1个答案

施飞雨
2023-03-14

在我看来,我会在Microsoft.Extensions.http中使用IHttpClientFactory,并创建一个自定义依赖项注入扩展,供类库的使用者使用:

public static class DemoClassServiceCollectionExtensions
{
    public static IServiceCollection AddDemoClass(
        this IServiceCollection services, 
        Func<HttpMessageHandler> configureHandler = null)
    {
        // Configure named HTTP client with primary message handler
        var builder= services.AddHttpClient(nameof(DemoClass));

        if (configureHandler == null)
        {
            builder = builder.ConfigurePrimaryHttpMessageHandler(
                () => new HttpClientHandler
                {
                    AllowAutoRedirect = false
                });
        }
        else
        {
            builder = builder.ConfigurePrimaryHttpMessageHandler(configureHandler);
        }

        services.AddTransient<DemoClass>();

        return services;
    }
}

DemoClass中,使用IHttpClientFactory创建命名HTTP客户端:

class DemoClass
{
    private readonly HttpClient _client;

    public DemoClass(IHttpClientFactory httpClientFactory)
    {
        // This named client will have pre-configured message handler
        _client = httpClientFactory.CreateClient(nameof(DemoClass));
    }

    public async Task DownloadSomethingAsync()
    {
        // omitted
    }
}

您可以要求使用者必须调用AddDemoClass才能使用DemoClass:

var services = new ServiceCollection();
services.AddDemoClass();

通过这种方式,您可以隐藏HTTP客户端构造的详细信息。

同时,在测试中,您可以模拟IHttpClientFactory以返回HttpClient用于测试目的。

 类似资料:
  • 在“依赖注入Actors”中,展示了如何将参数注入子Actors的构造函数中。父执行元使用injectedChild只允许将未注入的参数传递给子执行元(在子执行元创建时),然后让Guice注入其馀的参数。为此,它扩展了InjectedActorSupport并将子工厂注入构造函数: 但是启动父级并且不是执行元而是自定义ApplicationLoader的类怎么办?如何从那里启动父级演员?文件中没有

  • 我编写了以下来让Jackson将一个数组的整数序列化为JSON: 此处使用该类: 我想测试序列化程序的行为,并得出以下结论: 但是,不向写入任何内容。我做错了什么?

  • 问题内容: 我有一段代码,我不知道如何进行单元测试!该模块使用urllib2从外部XML提要(twitter,flickr,youtube等)中提取内容。这是一些伪代码: 我的第一个想法是腌制响应并加载它以进行测试,但是显然urllib的响应对象是不可序列化的(它引发了异常)。 仅从响应主体保存XML是不理想的,因为我的代码也使用标头信息。它旨在作用于响应对象。 当然,在单元测试中依赖外部数据源是

  • 我试图用Hibernate Validation 6.0.1定义约束定义,其中验证器位于相对于约束注释的不同位置(.jar/项目)。Aka,我有我想要验证的对象,它们位于带有注释定义的项目“api”中,但我将在项目“modules/common”中有验证器 我遵循文档中的描述。 配置文件 约束注释 验证器 我的问题我的问题是,如果我没有将“@约束(validatedby={})”放在注释中,我会得

  • 问题内容: 我已经开发了一种map-reduce程序。我写了习俗和课程。 我正在使用mapper和reducer 并对其进行单元测试。 我想知道如何对定制和类进行单元测试?测试这些类的最优选方法是什么? 问题答案: 答案中示例代码的经过编译和某种程度上的测

  • 我有以下方法,它采用UNIX时间戳并以天、小时或分钟的形式返回年龄。我想用JUnit单元测试它,但我不确定如何开始这样做,因为当前时间不断变化。有什么建议吗?谢谢! 方法如下: }