Unit testing FluentValidation rules for classes with sub/child classes
Asked Answered
B

4

13

Is it possible to write unit tests for fluentvalidation classes when the object we are validating has child classes that are also being validated.

As an example: My class looks like this

public class TestModel
{

    public class SubData
    {
        public int SubDataVal { get; set; }
    }

    public int ParentVal { get; set; }
    public SubData Sub { get; set; }

}

My validation logic looks like this:

public class TestModelValidator : AbstractValidator<TestModel>
{
    public TestModelValidator()
    {
        RuleFor(o => o.ParentVal).GreaterThan(0);
        RuleFor(o => o.Sub.SubDataVal).GreaterThan(0);
    }
}

And when I write the following unit test

    [Test]
    public void Should_have_error_when_val_is_zero()
    {
        validator = new TestModelValidator();
        validator.ShouldHaveValidationErrorFor(model => model.ParentVal, 0);
    }

I get a "System.NullReferenceException : Object reference not set to an instance of an object." exception from FluentValidation.TestHelper.ValidatorTester`2.ValidateError(T instanceToValidate)

(if I remove the RuleFor(o => o.Sub.SubDataVal).GreaterThan(0); line, then it works!)

Similarly if I try and unit test the actual child class with:

    [Test]
    public void Should_have_error_when_sub_dataVal_is_zero()
    {
        validator = new TestModelValidator();
        validator.ShouldHaveValidationErrorFor(model => model.Sub.SubDataVal, 0);
    }

I get a "System.Reflection.TargetException : Object does not match target type." from FluentValidation.TestHelper.ValidatorTester`2.ValidateError(T instanceToValidate)

Bringingup answered 17/12, 2012 at 13:50 Comment(0)
S
10

You can unit test models and child models but you will need to change your validation class to use a separate validator class which just validates the child model:

public class TestModelValidator : AbstractValidator<TestModel>
{
    public TestModelValidator()
    {
        RuleFor(o => o.ParentVal).GreaterThan(0);
        RuleFor(o => o.Sub).SetValidator(new SubDataValidator());
    }
}

public class SubDataValidator : AbstractValidator<SubData>
{
    public SubDataValidator()
    {
        RuleFor(o => o.SubDataVal).GreaterThan(0);
    }
}

You can then write your unit tests to test each validator or both together.

Surprise answered 23/12, 2012 at 17:11 Comment(3)
That is true, and would allow the "ShouldHaveValidationErrorFor" method to work. Personally I preferred to write my unit tests in a different way instead instead so that I don't have to change core code in order to make unit tests work.Bringingup
This isn't changing implementation just to make code testable - it is how Fluent is intended to be used, hence the presence of the "SetValidator" methodSurprise
By that I meant we didn't follow TDD. we already have a complex object, and it is already being validated by one fluent validator, I didn't want change existing working code so that my unit tests would pass.Bringingup
B
9

I have come to the conclusion that for this ShouldHaveValidationErrorFor is just not capabable of dealing with subclasses, so have resorted to doing it manually. i.e.

    [Test]
    public void Should_have_error_when_val_is_zero()
    {
        validator = new TestModelValidator();
        TestModel testRequest = new TestModel();
        //populate with dummy data
        var result = validator.Validate(testRequest);
        Assert.That(result.Errors.Any(o => o.PropertyName== "ParentVal"));
    }
Bringingup answered 18/12, 2012 at 11:54 Comment(0)
S
2

With MSTest and FluentAssertions you can write

[TestMethod]
public void Should_have_error_when_val_is_zero()
{
    // Given
    var validator = new TestModelValidator();
    var testModel = TestModel
    {
        ParentVal = 0
    }; // You should create a invalid TestModel object here

    // When
    validator.Validate(testModel).IsValid.Should().BeFalse();
}
Signesignet answered 29/7, 2020 at 13:21 Comment(0)
E
0

For anyone getting here as I did.

As all rules are executed during the Validation you need to set the whole model to reasonable values.

With current version you can do following:

//using FluentValidation;
//using FluentValidation.TestHelper;

[Fact]
public void Should_have_error_when_val_is_zero()
{
  var validator = new TestModelValidator();
  var model = new TestModel()
  {
    ParentVal = 0,
    Sub = new TestModel.SubData()
  };

  var result = validator.TestValidate(model);

  result.ShouldHaveValidationErrorFor(model => model.ParentVal);
  //result.ShouldHaveValidationErrorFor(nameof(TestModel.ParentVal));
}
Equity answered 24/2, 2023 at 20:3 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.