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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 19:05:25