C# Unit testing FluentValidation and mocking nested validator (.SetValidator) - Stack Overflow

admin2025-05-01  1

I'm working on a .NET 4.8 project and using FluentValidation v9.5.4. I can't update the version for reasons beyond my control.

Using the example classes from FluentValidation docs:

public class Customer 
{
    public string Name { get; set; }
    public Address Address { get; set; }
}
public class Address 
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string Town { get; set; }
    public string Country { get; set; }
    public string Postcode { get; set; }
}
public class AddressValidator : AbstractValidator<Address> 
{
    public AddressValidator()
    {
        RuleFor(address => address.Postcode).NotNull();
        //etc
    }
}
public class CustomerValidator : AbstractValidator<Customer> 
{
    public CustomerValidator(IValidator<Address> addressValidator)
    {
        RuleFor(customer => customer.Name).NotNull();
        RuleFor(customer => customer.Address).SetValidator(addressValidator);
    }
}

With that setup, the validators work as expected.

When unit testing, ideally I want to test only the code of the class and mock all its dependencies. So I create some unit test like this:

public class AddressValidatorTests
{
    private readonly AddressValidator _validator;

    public AddressValidatorTests()
    {
        _validator = new AddressValidator();
    }

    [Fact]
    public void Validate_Test()
    {
        var address = new Address
        {
            Line1 = "Line number 1",
            Line2 = "Line number 2",
            Town = "Small Town",
            Country = "Some Countery",
            Postcode = "658542"
        };

        var result = _validator.TestValidate(address);

        result.ShouldNotHaveAnyValidationErrors();
    }
}
public class CustomerValidatorTests
{
    private readonly CustomerValidator _validator;

    public CustomerValidatorTests()
    {
        var addressValidatorMock = new Mock<IValidator<Address>>();
        addressValidatorMock.Setup(m => m.Validate(It.IsAny<Address>()))
            .Returns(new FluentValidation.Results.ValidationResult());

        _validator = new CustomerValidator(addressValidatorMock.Object);
    }

    [Fact]
    public void Validate_Test()
    {
        var customer = new Customer
        {
            Name = "John Doe",
            Address = new Address()
        };

        var result = _validator.TestValidate(customer);

        result.ShouldNotHaveAnyValidationErrors();
    }
}

But, when I try to run Validate_Test() on CustomerValidatorTests I get an error:

System.NullReferenceException : Object reference not set to an instance of an object.

Stack Trace:
ChildValidatorAdaptor2.Validate(PropertyValidatorContext context) line 58 PropertyRule.InvokePropertyValidator(IValidationContext context, IPropertyValidator validator, String propertyName) line 519 <Validate>d__72.MoveNext() line 351 WhereEnumerableIterator1.MoveNext()
AbstractValidator1.Validate(ValidationContext1 context) line 95
IValidator.Validate(IValidationContext context) line 48
DefaultValidatorExtensions.Validate[T](IValidator1 validator, T instance, Action1 options) line 39
ValidationTestExtension.TestValidate[T](IValidator1 validator, T objectToTest, Action1 options) line 165
ValidationTestExtension.TestValidate[T](IValidator`1 validator, T objectToTest, String ruleSet) line 147
CustomerValidatorTests.Validate_Test() line 38

I'm thinking there is something else I need to setup on the mock but it's not clear what.

What am I missing here?

I'm working on a .NET 4.8 project and using FluentValidation v9.5.4. I can't update the version for reasons beyond my control.

Using the example classes from FluentValidation docs:

public class Customer 
{
    public string Name { get; set; }
    public Address Address { get; set; }
}
public class Address 
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string Town { get; set; }
    public string Country { get; set; }
    public string Postcode { get; set; }
}
public class AddressValidator : AbstractValidator<Address> 
{
    public AddressValidator()
    {
        RuleFor(address => address.Postcode).NotNull();
        //etc
    }
}
public class CustomerValidator : AbstractValidator<Customer> 
{
    public CustomerValidator(IValidator<Address> addressValidator)
    {
        RuleFor(customer => customer.Name).NotNull();
        RuleFor(customer => customer.Address).SetValidator(addressValidator);
    }
}

With that setup, the validators work as expected.

When unit testing, ideally I want to test only the code of the class and mock all its dependencies. So I create some unit test like this:

public class AddressValidatorTests
{
    private readonly AddressValidator _validator;

    public AddressValidatorTests()
    {
        _validator = new AddressValidator();
    }

    [Fact]
    public void Validate_Test()
    {
        var address = new Address
        {
            Line1 = "Line number 1",
            Line2 = "Line number 2",
            Town = "Small Town",
            Country = "Some Countery",
            Postcode = "658542"
        };

        var result = _validator.TestValidate(address);

        result.ShouldNotHaveAnyValidationErrors();
    }
}
public class CustomerValidatorTests
{
    private readonly CustomerValidator _validator;

    public CustomerValidatorTests()
    {
        var addressValidatorMock = new Mock<IValidator<Address>>();
        addressValidatorMock.Setup(m => m.Validate(It.IsAny<Address>()))
            .Returns(new FluentValidation.Results.ValidationResult());

        _validator = new CustomerValidator(addressValidatorMock.Object);
    }

    [Fact]
    public void Validate_Test()
    {
        var customer = new Customer
        {
            Name = "John Doe",
            Address = new Address()
        };

        var result = _validator.TestValidate(customer);

        result.ShouldNotHaveAnyValidationErrors();
    }
}

But, when I try to run Validate_Test() on CustomerValidatorTests I get an error:

System.NullReferenceException : Object reference not set to an instance of an object.

Stack Trace:
ChildValidatorAdaptor2.Validate(PropertyValidatorContext context) line 58 PropertyRule.InvokePropertyValidator(IValidationContext context, IPropertyValidator validator, String propertyName) line 519 <Validate>d__72.MoveNext() line 351 WhereEnumerableIterator1.MoveNext()
AbstractValidator1.Validate(ValidationContext1 context) line 95
IValidator.Validate(IValidationContext context) line 48
DefaultValidatorExtensions.Validate[T](IValidator1 validator, T instance, Action1 options) line 39
ValidationTestExtension.TestValidate[T](IValidator1 validator, T objectToTest, Action1 options) line 165
ValidationTestExtension.TestValidate[T](IValidator`1 validator, T objectToTest, String ruleSet) line 147
CustomerValidatorTests.Validate_Test() line 38

I'm thinking there is something else I need to setup on the mock but it's not clear what.

What am I missing here?

Share Improve this question edited Jan 2 at 20:59 Fildor 16.2k4 gold badges43 silver badges81 bronze badges asked Jan 2 at 20:44 JHJJHJ 3811 silver badge13 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 3

Change your Mock setup to use Validate(ValidationContext<T> ...):

public CustomerValidatorTests()
{
    var addressValidatorMock = new Mock<IValidator<Address>>();
    addressValidatorMock.Setup(m => m.Validate(It.IsAny<ValidationContext<Address>>()))
        .Returns(new FluentValidation.Results.ValidationResult());

    _validator = new CustomerValidator(addressValidatorMock.Object);
}

Due to the internal working of the TestValidate and nested validators this overload will be invoked.

转载请注明原文地址:http://anycun.com/QandA/1746097325a91628.html