.NET Core 6 API Xunit单元测试依赖注入报错问题排查
问题场景
在.NET Core 6 API项目中使用Xunit测试ICrpForcastsService接口的实现类CrpForcastsService,编写了测试类、Fixture类和Startup类,但运行测试时出现错误:
(No service for type 'Microsoft.AspNetCore.Builder.IApplicationBuilder' has been registered.) (The following constructor parameters did not have matching fixture data: ICrpForcastsService crpForcastsService)
测试类代码
public class ForcastTester: IClassFixture<InjectionFixture> { private readonly InjectionFixture injection; private readonly Psz.Core.Identity.Models.UserModel _user; private readonly ICrpForcastsService _crpForcastsService; public ForcastTester(InjectionFixture injection, ICrpForcastsService crpForcastsService) { this.injection = injection; var helpers = new TestingHelpers(); _user = helpers.GetDefaultTestUser(); _crpForcastsService = crpForcastsService; } [Fact] public void TestGetForecasts() { #region Arrange var expected = typeof(Psz.Core.Common.Models.ResponseModel<IEnumerable<Psz.Core.CRP.Models.Forcasts.ForcastHeaderModel>>); #endregion #region Act var result = _crpForcastsService.GetForcasts(_user); #endregion #region Assert Assert.NotNull(result); Assert.NotNull(result.Body); Assert.True(result.Success); Assert.IsType(expected, result); Assert.True(result.Body.Count() > 0); #endregion } }
Fixture类代码
public class InjectionFixture { public readonly TestServer server; private readonly HttpClient client; public InjectionFixture() { server = new TestServer(new WebHostBuilder().UseStartup<Startup>()); client = server.CreateClient(); } public IServiceProvider ServiceProvider => server.Host.Services; public void Dispose() { Dispose(true); } protected virtual void Dispose(bool disposing) { if(disposing) { server.Dispose(); client.Dispose(); } } }
Startup类代码
public class Startup { public Startup() { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } public virtual void ConfigureServices(IServiceCollection services) { services.AddScoped<ICrpForcastsService, CrpForcastsService>(); services.AddControllers(); } public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env) { } }
错误原因与修复步骤
1. 测试类构造函数无法直接注入服务
Xunit的Fixture机制不会自动解析并注入容器中的服务,测试类构造函数里的ICrpForcastsService参数无法被自动匹配。需要通过Fixture提供的ServiceProvider手动获取服务实例。
修改测试类构造函数:
public ForcastTester(InjectionFixture injection) { this.injection = injection; var helpers = new TestingHelpers(); _user = helpers.GetDefaultTestUser(); // 从Fixture的服务容器中获取目标服务 _crpForcastsService = injection.ServiceProvider.GetRequiredService<ICrpForcastsService>(); }
2. 替换测试服务器初始化方式(解决IApplicationBuilder注册问题)
自定义TestServer初始化方式在.NET Core 6中可能存在兼容性问题,建议改用官方推荐的WebApplicationFactory来创建测试服务器,它会自动处理应用构建的依赖项。
替换原InjectionFixture为:
public class InjectionFixture : WebApplicationFactory<Startup> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { // 可在此处覆盖或添加测试专用的服务配置 services.AddScoped<ICrpForcastsService, CrpForcastsService>(); }); } }
3. 确保测试项目能读取配置文件
右键测试项目中的appsettings.json文件,设置复制到输出目录属性为「如果较新则复制」,避免因配置文件缺失导致服务初始化异常。
4. 纯单元测试场景的轻量方案(可选)
如果只是测试服务的业务逻辑而非集成测试,可以直接构建独立的服务容器,无需启动测试服务器:
public class InjectionFixture : IDisposable { public IServiceProvider ServiceProvider { get; } public InjectionFixture() { var services = new ServiceCollection(); var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true) .Build(); services.AddSingleton<IConfiguration>(configuration); services.AddScoped<ICrpForcastsService, CrpForcastsService>(); // 添加服务依赖的其他组件 ServiceProvider = services.BuildServiceProvider(); } public void Dispose() { if (ServiceProvider is IDisposable disposable) { disposable.Dispose(); } } }
内容的提问来源于stack exchange,提问作者mohamed souilmi

