使用IOptions模式时,如何让选项对象属性在测试中强制必填?
问题描述
我认可IOptions模式从配置加载内容并通过验证保证选项完整性的设计,但在测试依赖该选项的组件时,存在可以仅初始化选项对象必要部分的问题。我的选项对象定义如下:
public sealed record SomeOptions { public string Name { get; set; } public string Description { get; set; } public Uri Location { get; set; } }
我希望在测试中创建SomeOptions实例时,强制Name和Location为必填项,和正常场景的验证逻辑一致,避免因使用无效实例导致测试隐患。常规做法是用构造函数强制必填,但IOptions构建配置时需要无参构造,会抛出InvalidOperationException。我目前添加了标注“DO NOT USE”的无参构造,但这显然不够完善,请问最优解决方案是什么?
解决方案
方案1:工厂方法封装实例创建
保留无参构造并限制访问范围(设为internal),同时提供公开的静态工厂方法,强制传入必填参数:
public sealed record SomeOptions { // 仅限IOptions框架和内部代码使用 internal SomeOptions() { } public string Name { get; set; } public string Description { get; set; } public Uri Location { get; set; } // 公开工厂方法,确保必填项不缺失 public static SomeOptions Create(string name, Uri location, string description = null) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("名称不能为空或空白", nameof(name)); if (location == null) throw new ArgumentNullException(nameof(location)); return new SomeOptions { Name = name, Location = location, Description = description ?? string.Empty }; } }
- 测试时必须通过
SomeOptions.Create(...)创建实例,从根源避免无效实例; - 通过
InternalsVisibleTo特性允许测试项目访问内部构造函数,不影响IOptions正常加载配置。
方案2:复用配置验证逻辑
给选项类添加数据验证特性,测试时手动触发验证,和生产环境逻辑保持一致:
using System.ComponentModel.DataAnnotations; public sealed record SomeOptions { [Required(AllowEmptyStrings = false)] public string Name { get; set; } public string Description { get; set; } [Required] public Uri Location { get; set; } }
测试中添加验证辅助方法:
// 测试通用验证方法 public static void ValidateOptions<T>(T options) where T : class { var validationContext = new ValidationContext(options); Validator.ValidateObject(options, validationContext, validateAllProperties: true); } // 测试代码示例 var testOptions = new SomeOptions { Name = "测试名称", Location = new Uri("https://example.com") }; ValidateOptions(testOptions); // 必填项缺失时会抛出ValidationException
这种方式完全复用生产环境的验证规则,既满足IOptions的无参构造要求,又能在测试中确保实例有效性。
方案3:私有构造+记录的With表达式(C# 9+)
利用记录类型的With特性,结合私有无参构造和公开带参构造:
using System.Text.Json.Serialization; public sealed record SomeOptions { // 标记为Json构造函数,供IOptions的JSON序列化使用 [JsonConstructor] private SomeOptions() { } // 公开带参构造,强制传入必填项 public SomeOptions(string name, Uri location) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("名称不能为空或空白", nameof(name)); if (location == null) throw new ArgumentNullException(nameof(location)); Name = name; Location = location; Description = string.Empty; } public string Name { get; init; } public string Description { get; init; } public Uri Location { get; init; } }
- 测试时必须通过带参构造创建实例,保证必填项存在;
- 记录的
With表达式可以方便修改可选属性:var baseOptions = new SomeOptions("测试", new Uri("https://example.com")); var modifiedOptions = baseOptions with { Description = "测试描述" };
内容的提问来源于stack exchange,提问作者sr28
相关产品推荐
相关产品推荐

