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

使用WebApplicationFactory测试配置Serilog的Web API控制器时,解决_apiConfiguration空引用异常的方法

WebApplicationFactory测试配置Serilog的Web API控制器时,解决_apiConfiguration空引用异常的方法

你遇到的问题我太熟悉了——WebApplicationFactory做集成测试时确实会跳过Program的Main方法,直接调用CreateHostBuilder,这就导致你在Main里初始化的_apiConfiguration静态变量完全没机会赋值,自然在配置Serilog的时候就炸空引用了。

下面给你两个实用的修复方案,你可以根据自己的需求选:


方案一:重构原项目代码,让配置加载逻辑内置到CreateHostBuilder里

最彻底的解决方式是把原本放在Main里的配置加载逻辑移到CreateHostBuilder内部,这样不管是正常启动还是测试启动,配置都会被正确初始化,再也不依赖外部静态变量:

修改后的Program.cs代码:

public class Program
{
    public static async Task Main(string[] args)
    {
        try
        {
            await CreateHostBuilder(args).Build().RunAsync();
        }
        catch (Exception ex)
        {
            // 这里处理异常日志
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                // 把Main里的配置加载逻辑移到这里
                var environment = hostingContext.HostingEnvironment;
                
                config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
                if (environment.IsDevelopment())
                {
                    config.AddJsonFile($"appsettings.{environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
                }
                config.AddEnvironmentVariables();
            })
            .ConfigureLogging((hostingContext, loggingBuilder) =>
            {
                // 直接用hostingContext里的配置,再也不用怕null了
                Log.Logger = new LoggerConfiguration()
                    .ReadFrom.Configuration(hostingContext.Configuration)
                    .Enrich.FromLogContext()
                    .Enrich.WithProperty("ApplicationName", "WeatherAPI")
                    .WriteToOtlpSerilog()
                    .CreateLogger();
                
                loggingBuilder.AddSerilog();
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

这种写法的好处是,生产代码和测试代码的启动逻辑完全统一,不会出现测试和生产行为不一致的情况。


方案二:在测试项目中重写WebApplicationFactory,覆盖配置逻辑

如果你不想改动生产代码,也可以在测试层直接覆盖配置和日志逻辑,绕开原项目的静态变量依赖:

[TestClass]
public class WeatherControllerTests : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        // 第一步:给测试环境加载专属配置
        builder.ConfigureAppConfiguration(config =>
        {
            var testConfig = new ConfigurationBuilder()
                .AddJsonFile("appsettings.Test.json", optional: false)
                .Build();
            
            config.AddConfiguration(testConfig);
        });

        // 第二步:重新配置Serilog,不依赖原项目的_apiConfiguration
        builder.ConfigureLogging(loggingBuilder =>
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Information()
                .Enrich.FromLogContext()
                .Enrich.WithProperty("ApplicationName", "WeatherAPI")
                // 测试时可以用控制台输出代替OTLP,避免依赖外部服务
                .WriteTo.Console()
                .CreateLogger();
            
            loggingBuilder.AddSerilog();
        });
    }

    [TestMethod]
    public async Task TestAsync()
    {
        var client = CreateClient();
        var response = await client.GetAsync("/weather/getforecast");
        Assert.IsNotNull(response);
    }
}

这种方式适合不想动生产代码的场景,直接在测试层把有问题的逻辑覆盖掉,快速解决测试报错的问题。


再啰嗦一句问题根源

WebApplicationFactory的设计初衷就是直接构建测试宿主,跳过Main方法里的自定义启动逻辑(比如你写的异常捕获、手动配置加载)。你原本把配置初始化放在Main里,测试时这部分代码根本没机会跑,_apiConfiguration自然就是null,空引用异常也就不可避免了。

不管选哪种方案,核心都是让配置在CreateHostBuilder的执行流程中被初始化,而不是依赖只有正常启动才会执行的Main方法。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:33:14