使用嵌套验证对复杂模型进行单元测试

时间:2013-03-18 20:49:19

标签: .net mocking tdd fluentvalidation

我正在使用fluentvalidation进行模型验证。我有一个类,有几个嵌套类或类集合,每个类都有自己的IValidator。最初我正在做这样的事情来设置嵌套的验证器:

RuleFor(foo => foo.Header).SetValidator(new FooHeaderValidator());

这非常有效。

当我开始实现更多嵌套验证器时,我开始意识到我的单元测试对于顶级验证是多么脆弱。基本上,对子验证器的任何更改都可能导致意外行为并导致测试失败。显然这是由于我直接实例化了子验证器。我现在正在通过构造函数注入获取该依赖项。这允许我嘲笑FooHeaderValidator

我现在测试失败,null reference例外来自流畅验证的某个地方。我只能假设某个地方有人要求我的模拟不供应。这是来自fluentvalidation的堆栈跟踪:

   at FluentValidation.Validators.ChildValidatorAdaptor.Validate(PropertyValidatorContext context)
   at FluentValidation.Validators.DelegatingValidator.Validate(PropertyValidatorContext context)
   at FluentValidation.Internal.PropertyRule.InvokePropertyValidator(ValidationContext context, IPropertyValidator validator, String propertyName)
   at FluentValidation.Internal.PropertyRule.<Validate>d__8.MoveNext()
   at System.Linq.Enumerable.<SelectManyIterator>d__14`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList(IEnumerable`1 source)
   at FluentValidation.AbstractValidator`1.Validate(ValidationContext`1 context)
   at FluentValidation.AbstractValidator`1.Validate(T instance)

有没有人遇到这个并且知道我错过了什么?我是否因为嘲笑这些验证员而感到疯狂?

3 个答案:

答案 0 :(得分:6)

所以这实际上很简单。答案是您需要为接受Validate的{​​{1}}覆盖设置模拟。在RhinoMocks中,这看起来像:

ValidationContext<T>

与Moq非常相似:

public static IValidator<T> GetMockedNestedValidator<T>()
{
    var mockedValidator = MockRepository.GenerateMock<IValidator<T>>();
    abstractValidator.Stub(x => x.Validate(Arg<ValidationContext<T>>.Is.Anything)).Return(new ValidationResult());
    return mockedValidator;
}

答案 1 :(得分:0)

很好的回答内森。

这是我对具有五个属性的模型的完整实现和单元测试:

/// <summary>
/// Validator for the MyModel.
/// </summary>
public class Validator : AbstractValidator<MyModel>
{
    /// <summary>
    /// Validate the MyModel.
    /// </summary>
    public Validator(
        IValidator<PropertyAModel> propertyAValidator,
        IValidator<PropertyBModel> propertyBValidator,
        IValidator<PropertyCModel> propertyCValidator,
        IValidator<PropertyDModel> propertyDValidator,
        IValidator<PropertyEModel> propertyEValidator)
    {
        RuleFor(o => o.PropertyA).SetValidator(propertyAValidator);
        RuleFor(o => o.PropertyB).SetValidator(propertyBValidator);
        RuleFor(o => o.PropertyC).SetValidator(propertyCValidator);
        RuleFor(o => o.PropertyD).SetValidator(propertyDValidator);
        RuleFor(o => o.PropertyE).SetValidator(propertyEValidator);
    }
}


[TestFixture]
public class ValidatorTests : TestBase
{
    private Mock<IValidator<PropertyAModel>> _mockPropertyAValidator;
    private Mock<IValidator<PropertyBModel>> _mockPropertyBValidator;
    private Mock<IValidator<PropertyCModel>> _mockPropertyCValidator;
    private Mock<IValidator<PropertyDModel>> _mockPropertyDValidator;
    private Mock<IValidator<PropertyEModel>> _mockPropertyEValidator;
    private Validator _validator;

    /// <Summary>
    /// Setup the unit test.
    /// </Summary>
    [SetUp]
    public void SetUp()
    {
        _mockPropertyAValidator = GetMockNestedValidator<PropertyAModel>();
        _mockPropertyBValidator = GetMockNestedValidator<PropertyBModel>();
        _mockPropertyCValidator = GetMockNestedValidator<PropertyCModel>();
        _mockPropertyDValidator = GetMockNestedValidator<PropertyDModel>();
        _mockPropertyEValidator = GetMockNestedValidator<PropertyEModel>();

        _validator = new Validator(
             _mockPropertyAValidator.Object,
             _mockPropertyBValidator.Object,
             _mockPropertyCValidator.Object,
             _mockPropertyDValidator.Object,
             _mockPropertyEValidator.Object);
    }

    [Test]
    public void Verify_Is_Successful()
    {
        //
        // Arrange.
        //
        var model = new MyModel
        {
            PropertyA = new PropertyAModel(),
            PropertyB = new PropertyBModel(),
            PropertyC = new PropertyCModel(),
            PropertyD = new PropertyDModel(),
            PropertyE = new PropertyEModel()
        };

        //
        // Act.
        //
        _validator.Validate(model);

        //
        // Assert.
        //
        VerifyMockNestedValidator(_mockPropertyAValidator);
        VerifyMockNestedValidator(_mockPropertyBValidator);
        VerifyMockNestedValidator(_mockPropertyCValidator);
        VerifyMockNestedValidator(_mockPropertyDValidator);
        VerifyMockNestedValidator(_mockPropertyEValidator);
    }


    /// <summary>
    /// Get a mock validator for a nested model type.
    /// </summary>
    /// <typeparam name="T">The type of the nested model.</typeparam>
    /// <returns>The mock validator.</returns>
    public static Mock<IValidator<T>> GetMockNestedValidator<T>()
    {
        var mockValidator = new Mock<IValidator<T>>();
        mockValidator.Setup(x => x.Validate(It.IsAny<ValidationContext>())).Returns(new ValidationResult());
        return mockValidator;
    }

    /// <summary>
    /// Verify the mock validator for a nested model has called the Validate() method exactly once.
    /// </summary>
    /// <typeparam name="T">The type of the nested model.</typeparam>
    /// <param name="mockValidator">The mock validator to verify.</param>
    public static void VerifyMockNestedValidator<T>(Mock<IValidator<T>> mockValidator)
    {
        mockValidator.Verify(x => x.Validate(It.IsAny<ValidationContext>()), Times.Once());
    }

答案 2 :(得分:0)

只是为那些使用异步验证的相同问题的人添加。 我需要覆盖以下(使用NSubstitute)

validator.ValidateAsync(Arg.Any<ValidationContext>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(new ValidationResult()));

注意:在我的情况下,我需要覆盖非泛型ValidationContext