You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决依赖IHostApplicationLifetime的IHostedService单元测试注入失败问题

无法解析IHostApplicationLifetime的单元测试问题

我在为实现IHostedService接口的Princess类编写单元测试时遇到问题:该类构造函数依赖IServiceScopeFactory、IHostApplicationLifetime和IOptions<OutputFilePrefix>,测试代码构建ServiceProvider后获取服务时,抛出无法解析IHostApplicationLifetime服务的错误,手动添加该依赖也未解决。

待测试类构造函数

public Princess(IServiceScopeFactory serviceScopeFactory, IHostApplicationLifetime hostApplicationLifetime, IOptions<OutputFilePrefix> options)
{
    _serviceScopeFactory = serviceScopeFactory;
    _hostApplicationLifetime = hostApplicationLifetime;
    Console.WriteLine(options.Value);
}

报错的测试代码

IServiceCollection services = new ServiceCollection();
services.AddScoped<IContenderGenerator>(sp => stabGenerator);
services.AddScoped<IHall, Hall>();
services.AddScoped<IFriend, SimpleFriend>();
services.AddScoped<IResultSaver, FileResultSaver>();
services.AddHostedService<Princess>();

var serviceProvider = services.BuildServiceProvider();


var princess = serviceProvider.GetRequiredService<IHostedService>() as Princess;
Console.WriteLine(princess);
Assert.Greater(princess.ChooseContender().Rating, 10);

错误堆栈信息

Unable to resolve service for type 'Microsoft.Extensions.Hosting.IHostApplicationLifetime' while attempting to activate 'pickyPride2.Princess'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)

解决方案

1. 使用Host.CreateDefaultBuilder构建完整宿主环境

手动搭建的ServiceCollection不会自动注册IHostApplicationLifetime这类宿主核心服务,而Host.CreateDefaultBuilder会自动配置所有宿主环境所需的基础服务,包括IHostApplicationLifetime、IOptions框架等。

示例代码:

// 准备模拟的IContenderGenerator实例
var stabGenerator = new Mock<IContenderGenerator>().Object;

var host = Host.CreateDefaultBuilder()
    .ConfigureServices(services =>
    {
        services.AddScoped<IContenderGenerator>(sp => stabGenerator);
        services.AddScoped<IHall, Hall>();
        services.AddScoped<IFriend, SimpleFriend>();
        services.AddScoped<IResultSaver, FileResultSaver>();
        services.AddHostedService<Princess>();
        
        // 配置测试用的OutputFilePrefix选项
        services.Configure<OutputFilePrefix>(options =>
        {
            options.Prefix = "test-output-prefix";
        });
    })
    .Build();

// 创建服务作用域并获取Princess实例
using var scope = host.Services.CreateScope();
var princess = scope.ServiceProvider.GetRequiredService<IHostedService>() as Princess;

// 执行断言
Assert.NotNull(princess);
Assert.Greater(princess.ChooseContender().Rating, 10);

// 若需要触发HostedService的生命周期方法,可启动并停止宿主
await host.StartAsync();
await host.StopAsync();

2. 手动注册模拟的IHostApplicationLifetime

如果不想依赖完整宿主构建器,可以用Moq等框架手动模拟IHostApplicationLifetime实例,同时确保IOptions<OutputFilePrefix>被正确配置:

示例代码:

IServiceCollection services = new ServiceCollection();
services.AddScoped<IContenderGenerator>(sp => stabGenerator);
services.AddScoped<IHall, Hall>();
services.AddScoped<IFriend, SimpleFriend>();
services.AddScoped<IResultSaver, FileResultSaver>();
services.AddHostedService<Princess>();

// 注册并配置IOptions<OutputFilePrefix>
services.Configure<OutputFilePrefix>(options =>
{
    options.Prefix = "test-output-prefix";
});

// 模拟IHostApplicationLifetime
var mockLifetime = new Mock<IHostApplicationLifetime>();
// 根据测试需求模拟生命周期信号(如启动完成的Token)
mockLifetime.Setup(x => x.ApplicationStarted).Returns(new CancellationTokenSource().Token);
services.AddSingleton(mockLifetime.Object);

var serviceProvider = services.BuildServiceProvider();

var princess = serviceProvider.GetRequiredService<IHostedService>() as Princess;
Console.WriteLine(princess);
Assert.Greater(princess.ChooseContender().Rating, 10);

关键注意点

  • IHostApplicationLifetime是.NET宿主环境的专属服务,只有通过Host系列构建器才会自动注册,手动维护ServiceCollection不会包含该服务。
  • 必须同步配置IOptions<OutputFilePrefix>,否则解决IHostApplicationLifetime问题后,会出现IOptions解析失败的新错误。

内容的提问来源于stack exchange,提问作者Йцу Фыв

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 02:01:14