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

.NET 7单元测试中从appsettings加载的数组丢失空字符串问题

解决.NET 7中TestServer加载配置时空字符串数组元素丢失的问题

.NET 7项目里遇到这么个情况:

  • appsettings里的二维字符串数组包含空字符串,API里用config.GetSection("AppSettings").GetSection("Mapping").Get<string[][]>()能正常拿到每个子数组的3个元素
  • 但单元测试用TestServer加载同一份配置时,空字符串直接被删了,导致断言失败;可直接用ConfigurationBuilder构建的ConfigurationRoot却能正常读取完整数组
  • 切回.NET 6就没问题,确定是.NET 7的配置逻辑变了,而且降级TestHost之类的NuGet包也没用
  • 要求不能改API的业务代码,只能调单元测试代码解决

方案一:自定义JSON配置提供器(推荐)

TestServer在.NET 7里的配置加载链和直接构建ConfigurationRoot不一样,我们可以自己写个配置提供器,强制保留空字符串元素。

  1. 编写自定义配置源和提供器(放在测试项目中):
public class PreserveEmptyStringJsonConfigurationSource : JsonConfigurationSource
{
    public override IConfigurationProvider Build(IConfigurationBuilder builder)
    {
        EnsureDefaults(builder);
        return new PreserveEmptyStringJsonConfigurationProvider(this);
    }
}

public class PreserveEmptyStringJsonConfigurationProvider : JsonConfigurationProvider
{
    public PreserveEmptyStringJsonConfigurationProvider(PreserveEmptyStringJsonConfigurationSource source)
        : base(source)
    {
    }

    protected override void Load(Stream stream)
    {
        var jsonDoc = JsonDocument.Parse(stream, new JsonDocumentOptions { AllowTrailingCommas = true });
        if (jsonDoc.RootElement.ValueKind != JsonValueKind.Object)
        {
            throw new FormatException($"配置文件 '{Source.Path}' 不是有效的JSON对象");
        }

        var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        TraverseElement(jsonDoc.RootElement, null, data);
        Data = data;
    }

    private void TraverseElement(JsonElement element, string? path, Dictionary<string, string> data)
    {
        switch (element.ValueKind)
        {
            case JsonValueKind.Object:
                foreach (var prop in element.EnumerateObject())
                {
                    var newPath = string.IsNullOrEmpty(path) ? prop.Name : $"{path}:{prop.Name}";
                    TraverseElement(prop.Value, newPath, data);
                }
                break;
            case JsonValueKind.Array:
                for (int i = 0; i < element.GetArrayLength(); i++)
                {
                    var newPath = string.IsNullOrEmpty(path) ? $"{i}" : $"{path}:{i}";
                    TraverseElement(element[i], newPath, data);
                }
                break;
            case JsonValueKind.String:
                // 不管是不是空字符串,直接存入配置字典
                data[path!] = element.GetString()!;
                break;
            case JsonValueKind.Number:
            case JsonValueKind.True:
            case JsonValueKind.False:
                data[path!] = element.ToString();
                break;
            case JsonValueKind.Null:
                data[path!] = null!;
                break;
            default:
                throw new FormatException($"不支持的JSON类型 '{element.ValueKind}',路径:{path}");
        }
    }
}
  1. 单元测试中使用自定义配置源构建TestServer:
[Fact]
public async Task Test_Mapping_Configuration()
{
    // 用自定义配置源加载appsettings,确保空字符串不丢失
    var configBuilder = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .Add(new PreserveEmptyStringJsonConfigurationSource
        {
            Path = "appsettings.json",
            Optional = false,
            ReloadOnChange = false
        })
        .AddEnvironmentVariables();

    var config = configBuilder.Build();

    // 给TestServer传入这个自定义配置启动
    var webHost = new WebHostBuilder()
        .UseConfiguration(config)
        .UseStartup<Startup>(); // 替换为你的项目Startup类

    using var server = new TestServer(webHost);
    var client = server.CreateClient();

    // 执行测试请求
    var response = await client.GetAsync("/api/your-endpoint");
    response.EnsureSuccessStatusCode();

    // 此处执行断言逻辑,配置中的空字符串已被正常保留
}

方案二:直接替换TestServer的配置实例

如果不想编写自定义提供器,也可以先构建一个能正确读取空字符串的配置,再替换TestServer服务容器中的IConfiguration实例:

[Fact]
public async Task Test_Mapping_Configuration()
{
    // 先构建一个能正确读取空字符串的配置实例
    var correctConfig = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json")
        .Build();

    // 启动TestServer
    var webHost = new WebHostBuilder()
        .UseStartup<Startup>();

    using var server = new TestServer(webHost);

    // 替换服务容器中的IConfiguration为正确实例
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddSingleton<IConfiguration>(correctConfig);
    var serviceProvider = serviceCollection.BuildServiceProvider();

    // 直接用正确配置做断言验证
    var mapping = correctConfig.GetSection("AppSettings").GetSection("Mapping").Get<string[][]>();
    Assert.Equal(3, mapping[0].Length); // 断言现在可以正常通过

    // 继续执行API测试逻辑
    var client = server.CreateClient();
    // ...
}

问题原因说明

.NET 7中,TestServer依赖的WebHost默认配置加载逻辑会过滤JSON数组中的空字符串元素;但直接使用ConfigurationBuilder时,用的是原始的JsonConfigurationProvider,不会做过滤处理。通过自定义配置提供器对齐逻辑,或者直接替换TestServer的配置实例,就能让单元测试的配置行为和API运行时一致,无需修改API业务代码。

内容的提问来源于stack exchange,提问作者majo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 17:40:29