如何在测试中用Options模式以实例替换应用配置?
如何在测试中直接传入Options实例配置ASP.NET DI容器
我使用C# Options模式定义了如下配置类型:
public class MySettings { public bool IsEnabled { get; set; } public int NumberOfDays { get; set; } }
在测试场景中,我希望通过CustomWebApplicationFactory配置DI容器,让它始终提供同一个MySettings实例。最初尝试直接传入实例但无法编译(因为Configure<T>不接受T类型的实例参数):
public class CustomWebApplicationFactory : WebApplicationFactory<Startup> { private readonly MySettings _settings; public CustomWebApplicationFactory(MySettings settings) : base() => _settings = settings; protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); // 编译失败:Configure<T>不接受T实例 builder.ConfigureServices(services => services.Configure<MySettings>(_settings)); } }
目前我通过传入Action<MySettings>的方式解决,但希望能直接传入实例提升测试代码的可读性:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup> { private readonly Action<MySettings> _configureSettings; public CustomWebApplicationFactory(Action<MySettings> configureSettings) : base() => _configureSettings = configureSettings; protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); builder.ConfigureServices(services => services.Configure<MySettings>(_configureSettings)); } }
解决方案
有两种简洁的方式实现直接传入实例的需求:
方法1:封装扩展方法实现实例配置
通过扩展方法封装属性复制逻辑,让Configure<T>支持直接接收实例:
public static class ServiceCollectionExtensions { public static IServiceCollection Configure<T>(this IServiceCollection services, T settings) where T : class, new() { return services.Configure<T>(opt => { // 反射复制所有公共实例属性值 var properties = typeof(T).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); foreach (var prop in properties) { prop.SetValue(opt, prop.GetValue(settings)); } }); } }
修改CustomWebApplicationFactory即可直接传入实例:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup> { private readonly MySettings _settings; public CustomWebApplicationFactory(MySettings settings) : base() => _settings = settings; protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); // 现在可以直接传入实例 builder.ConfigureServices(services => services.Configure(_settings)); } }
方法2:直接注册IOptions<MySettings>实例
如果不需要Options的配置管道(比如验证、后期配置),可以直接替换DI中IOptions<MySettings>的注册:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup> { private readonly MySettings _settings; public CustomWebApplicationFactory(MySettings settings) : base() => _settings = settings; protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); builder.ConfigureServices(services => { // 移除原有IOptions注册(避免冲突) var existingOptions = services.SingleOrDefault(d => d.ServiceType == typeof(IOptions<MySettings>)); if (existingOptions != null) { services.Remove(existingOptions); } // 注册我们的实例包装为IOptions services.AddSingleton(Options.Create(_settings)); }); } }
测试代码示例
修改后,测试代码可以直接创建实例传入,可读性大幅提升:
// 直接构造测试用配置实例 var testSettings = new MySettings { IsEnabled = true, NumberOfDays = 7 }; // 传入实例创建工厂 var factory = new CustomWebApplicationFactory(testSettings); // 使用工厂创建测试客户端 using var client = factory.CreateClient(); // ... 执行测试逻辑
内容的提问来源于stack exchange,提问作者mu88
相关产品推荐
相关产品推荐

