解决依赖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,提问作者Йцу Фыв
相关产品推荐
相关产品推荐

