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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 14:39:59