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

Fluent Validation:使用自定义属性验证器的单元测试未出预期错误怎么办?

问题分析与解决方案

你的问题出在验证器初始化时机和依赖传递方式上,以下是具体原因和修复步骤:

核心问题

你在定义验证规则时,直接在SetValidator中new UniqueValueValidator并传入了_repository.Queryable()——这会在验证器构造阶段就获取到IQueryable实例,而不是在验证请求发生时动态获取。后续单元测试中mock的_peopleRepositoryMock.Setup(x => x.Queryable()).Returns(people)并不会生效,因为验证器内部已经持有了构造时的旧IQueryable对象,根本不会使用你mock后的数据源。

修复方案

不要直接给验证器传入IQueryable,而是传入整个Repository实例,让验证器在验证逻辑执行时动态调用Queryable()获取数据源。

第一步:修改UniqueValueValidator的构造逻辑

调整验证器,让它依赖Repository而非直接依赖IQueryable:

public class UniqueValueValidator<TInput, TEntity, TProperty> : AbstractValidator<TInput>
{
    private readonly IRepository<TEntity> _repository;
    private readonly Expression<Func<TEntity, TProperty>> _entityPropertySelector;
    private readonly Expression<Func<TInput, TProperty>> _inputPropertySelector;

    // 构造函数接收Repository和属性选择器,而非直接传入IQueryable
    public UniqueValueValidator(
        IRepository<TEntity> repository,
        Expression<Func<TEntity, TProperty>> entityPropertySelector,
        Expression<Func<TInput, TProperty>> inputPropertySelector)
    {
        _repository = repository;
        _entityPropertySelector = entityPropertySelector;
        _inputPropertySelector = inputPropertySelector;

        // 验证逻辑:动态查询Repository的Queryable
        RuleFor(_inputPropertySelector)
            .MustAsync(async (inputModel, propertyValue, cancellationToken) =>
            {
                // 验证时才获取最新的IQueryable
                var exists = await _repository.Queryable()
                    .AnyAsync(entity => 
                        EqualityComparer<TProperty>.Default.Equals(
                            _entityPropertySelector.Compile()(entity), 
                            propertyValue
                        ), 
                        cancellationToken);
                return !exists;
            })
            .WithMessage("Duplicate value");
    }
}

第二步:调整验证规则的定义

在你的PersonValidator中,修改为传入Repository和属性选择器:

RuleFor(e => e.Name)
    .SetValidator(new UniqueValueValidator<PersonInput, Person, string>(
        _repository, 
        entity => entity.Name, 
        input => input.Name
    ))
    .WithMessage("Duplicate name");

第三步:修正单元测试逻辑

另外注意你的测试方法名ShouldNotHaveValidationError_WhenNameIsUnique和测试逻辑矛盾——你传入的是重复的Name,应该断言存在验证错误,同时确保mock的数据源正确包含重复值:

public void ShouldHaveValidationError_WhenNameIsDuplicate()
{
    // 构造包含"Al"的模拟数据源
    var people = new List<Person> { new Person { Name = "Al" } }.AsQueryable();
    _peopleRepositoryMock.Setup(x => x.Queryable()).Returns(people);

    // 传入重复的Name
    var model = new PersonInputModel{ Name = "Al" };

    var result = _validator.TestValidate(model);

    // 断言Name字段存在验证错误
    result.ShouldHaveValidationErrorFor(person => person.Name);
}

关于是否需要模拟UniqueValueValidator

不需要。UniqueValueValidator是你自定义的验证逻辑,属于需要测试的目标对象之一,模拟它会让测试失去意义。你只需要模拟它依赖的外部资源(比如Repository),以此控制测试数据,验证它的逻辑是否正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 23:45:45