.NET 8控制台应用集成测试:如何复用Host并修改配置
.NET 8控制台应用集成测试:复用Host并自定义配置
问题背景
我有一个.NET 8控制台应用,核心配置代码如下(Program.cs):
var builder = Host.CreateApplicationBuilder(args); builder.Services.AddSingleton<Main>(); builder.Services.AddHttpClient(); builder.Services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); }); using var host = builder.Build(); await host.Services.GetRequiredService<Main>().ExecuteAsync();
希望在集成测试项目中复用这个Host的基础配置,同时实现类似ASP.NET Core Web应用集成测试的能力:重写部分服务、修改环境名称。期望的测试逻辑伪代码如下:
public class MyTest { private IHost _host; void Setup() { _host = CreateHost(); _host.ConfigureTestServices(services => { services.AddTransient<IFooService, MockService>(); }); _host.UseEnvironment("Test"); } void TearDown() { _host.Dispose(); } public async Task Test() { var sender = _host.Services.GetRequiredService<ISender>(); var result = await sender.Send(new Query()); Assert.Equal(1, result); } }
实现方案
1. 让控制台项目的内部类型可被测试项目访问
.NET 8顶级语句生成的Program类默认是internal,需要在控制台项目中添加InternalsVisibleTo特性,允许测试项目访问其内部成员:
- 新建或修改
Properties/AssemblyInfo.cs,添加:
[assembly: InternalsVisibleTo("你的测试项目名称")]
2. 封装原项目的服务配置(可选但推荐)
把原Program.cs中的服务注册逻辑提取到扩展方法中,避免测试项目重复代码:
// 控制台项目中添加扩展类 public static class ApplicationServiceExtensions { public static IServiceCollection AddApplicationCoreServices(this IServiceCollection services) { services.AddSingleton<Main>(); services.AddHttpClient(); services.AddMediatR(cfg => { cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); }); return services; } }
修改原Program.cs使用该扩展:
var builder = Host.CreateApplicationBuilder(args); // 复用扩展方法注册服务 builder.Services.AddApplicationCoreServices(); using var host = builder.Build(); await host.Services.GetRequiredService<Main>().ExecuteAsync();
3. 自定义Host工厂(模仿WebApplicationFactory)
实现一个工厂类封装Host的创建和配置逻辑,方便复用:
// 测试项目中添加工厂类 public class ApplicationHostFactory { private readonly Action<IHostBuilder> _customConfigure; public ApplicationHostFactory(Action<IHostBuilder> customConfigure = null) { _customConfigure = customConfigure; } public IHost CreateHost() { var hostBuilder = Host.CreateDefaultBuilder() // 默认设置测试环境 .UseEnvironment("Test") .ConfigureServices((context, services) => { // 复用原项目的核心服务配置 services.AddApplicationCoreServices(); }); // 允许外部传入自定义配置(比如替换服务) _customConfigure?.Invoke(hostBuilder); var host = hostBuilder.Build(); host.Start(); return host; } }
4. 编写集成测试
使用工厂类创建Host,替换目标服务并执行测试:
public class MyTests : IDisposable { private readonly IHost _host; public MyTests() { _host = new ApplicationHostFactory(builder => { builder.ConfigureServices(services => { // 替换原有服务:先移除再添加模拟实现 var existingDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IFooService)); if (existingDescriptor != null) { services.Remove(existingDescriptor); } services.AddTransient<IFooService, MockFooService>(); }); }).CreateHost(); } public void Dispose() { _host.StopAsync().Wait(); _host.Dispose(); } [Fact] public async Task Query_Should_Return_Expected_Result() { var sender = _host.Services.GetRequiredService<ISender>(); var result = await sender.Send(new SampleQuery()); Assert.Equal(1, result); } }
简化版:直接在测试中构建Host
如果不需要复用工厂,也可以直接在测试的初始化方法中构建Host:
public class MyTests : IDisposable { private IHost _host; [SetUp] public void Setup() { _host = Host.CreateDefaultBuilder() .UseEnvironment("Test") .ConfigureServices(services => { // 复用原项目核心服务 services.AddApplicationCoreServices(); // 替换服务 services.Replace(ServiceDescriptor.Transient<IFooService, MockFooService>()); }) .Build(); _host.Start(); } [TearDown] public void TearDown() { _host.StopAsync().Wait(); _host.Dispose(); } [Test] public async Task Sample_Test() { var sender = _host.Services.GetRequiredService<ISender>(); var result = await sender.Send(new SampleQuery()); Assert.Equal(1, result); } }
内容的提问来源于stack exchange,提问作者filur
相关产品推荐
相关产品推荐

