magritte
magritte

Reputation: 7636

How to unit test child validators with When() condition with FluentValidation.TestHelper

The extension method .ShouldHaveChildValidator() in the FluentValidation.TestHelper namespace doesn't have an overload that takes the model. How do I then test that the child validators are set up correctly when using a When() clause like in the following example?

E.g.

public class ParentModel
{
    public bool SomeCheckbox { get; set; }

    public ChildModel SomeProperty { get; set; }
}

public class ParentModelValidator : AbstractValidator<ParentModel>
{
    RuleFor(m => m.SomeProperty)
            .SetValidator(new ChildModelValidator())
            .When(m => m.SomeCheckbox);
}

I want to Assert that if SomeCheckbox is true, then the child validator is present, and if SomeCheckbox is false, then the child validator isn't present.

I have the following so far in the unit test:

ParentModelValidator validator = new ParentModelValidator();
validator.ShouldHaveChildValidator(
    m => m.SomeProperty, 
    typeof(ChildModelValidator));

but that doesn't take into account the .When() condition.

I notice other methods in the FluentValidation.TestHelper namespace such as .ShouldHaveValidationErrorFor() have an overload that takes the model, so it's easy to test a simple property type with a When() clause by setting up a model that satisfies the precondition.

Any ideas?

Upvotes: 4

Views: 2789

Answers (2)

jmzagorski
jmzagorski

Reputation: 1155

very late to the game here, but I just started using FluentValidation and that was my solution

  public class ParentValidator: AbstractValidator<ParentModel>
  {
    public ParentValidator()
    {
      // other rules here

      // use == for bool?
      When(model => model.SomeBoolProperty == false, () => RuleFor(model => model.ChildClass).SetValidator(new ChildClassValidator()));
    }
  }

  public class ChildClassValidator: AbstractValidator<ChildClass>
  {
    public ChildClassValidator()
    {
      this
        .RuleFor(model => model.SomeProperty).NotNull();
    }
  } 

then the test is

[TestMethod]
public void ParentValidator_should_have_error_in_child_class_property_when_bool_is_false_on_parent()
{
  // Arrange - API does not support typical unit test
  var validator = new ParentValidator()
  var foo = new ParentModel() { SomeBoolProperty = false };
  foo.ChildClass.SomeProperty = null;

  // Act
  var result = validator.Validate(foo);

  // Assert - using FluentAssertions
  result.Errors.Should().Contain(err => err.PropertyName == "ChildClass.SomeProperty");
}

Upvotes: 1

MarkG
MarkG

Reputation: 1869

Here's a snippet of how I achieve this:

public class ParentModelSimpleValidator : AbstractValidator<ParentModel>
{
    public ParentModelSimpleValidator()
    {
        When(x => x.HasChild, () =>
            RuleFor(x => x.Child)
                .SetValidator(new ChildModelSimpleValidator()));
    }
}

public class ChildModelSimpleValidator : AbstractValidator<ChildModel>
{
    public ChildModelSimpleValidator()
    {
        RuleFor(x => x.ChildName)
            .NotEmpty()
            .WithMessage("Whatever");
    }
}

Here's the relevant simplified models:

[Validator(typeof(ParentModelSimpleValidator))]
public class ParentModel
{
    public bool HasChild { get { return Child != null; } }
    public ChildModel Child { get; set; }
}

[Validator(typeof(ChildModelSimpleValidator))]
public class ChildModel
{
    public string ChildName { get; set; }
    public int? ChildAge { get; set; }
}

Here's a sample unit test:

[TestMethod]
public void ShouldValidateChildIfParentHasChild()
{
    var validator = new ParentModelSimpleValidator();

    var model = new ParentModel
    {
        ParentName = "AABBC",
        Child = new ChildModel { ChildName = string.Empty }
    };

    validator.ShouldHaveErrorMessage(model, "Whatever");
}

Upvotes: 2

Related Questions