MatterOfFact
MatterOfFact

Reputation: 1565

How to unit test a single Fluent Validation Rule wit NUnit?

I've got a fluent validator config which looks like this:

        public class Validator : AbstractValidator<Command>
        {
            public Validator(IDbContext dbContext)
            {
                RuleFor(c => c.PersonId)
                    .EntityExists<Command, Person>(dbContext, nameof(Command.PersonId));

                RuleFor(c => c.ProjectId)
                    .Cascade(CascadeMode.Stop)
                    .EntityExists<Command, Project>(dbContext, nameof(Command.ProjectId))
                    .MustAsync(async (projectId, cancellation) => !(await dbContext.FindAsync<Project>(new object[] { projectId }, cancellation)).IsCompleted)
                    .WithMessage("The project is completed. You may not set the participation of a completed project");

                RuleFor(c => c.StatusId)
                    .EntityExists<Command, Status>(dbContext, nameof(Command.StatusId))

                ...
            }
        }

I'd like to unit test the second rule with NUnit, NSubstitute and the Fluent Validation Extensions:

        [Test]
        public void Should_Not_Have_Validation_Error_If_Valid_ProjectId_Is_Supplied()
        {
            _dbContext.EntityExistsAsync<Project>(Arg.Any<Guid>(), Arg.Any<CancellationToken>()).Returns(true);
            _validator.ShouldNotHaveValidationErrorFor(command => command.ProjectId, Guid.NewGuid());
        }

The test is failing due to the missing PersonId. I'm testing the validation rule for the ProjectId, but the test includes the validation rule for the PersonId. If I mock the dbContext to make the PersonId rule pass, the test failes because of the missing StatusId which is also a separately defined validation rule.

How can I test a rule for one separate property without having to mock the rules for all other properties in the model?

Upvotes: 0

Views: 1958

Answers (2)

MatterOfFact
MatterOfFact

Reputation: 1565

With the hint from @Евгений Елисеев (thanks!) I was able to write test extensions which test only the rules of a single property:

        public static IEnumerable<ValidationFailure> ShouldHaveValidationErrorForExact<T, TValue>(this IValidator<T> validator,
            Expression<Func<T, TValue>> expression, TValue value) where T : class, new()
        {
            var instanceToValidate = new T();

            var memberAccessor = new MemberAccessor<T, TValue>(expression, true);
            memberAccessor.Set(instanceToValidate, value);

            TestValidationResult<T> testValidationResult = validator.TestValidate(instanceToValidate, opt => opt.IncludeProperties(memberAccessor.Member.Name));
            return testValidationResult.ShouldHaveValidationErrorFor(expression);
        }

        public static IEnumerable<ValidationFailure> ShouldHaveValidationErrorForExact<T, TValue>(this IValidator<T> validator, Expression<Func<T, TValue>> expression, T objectToTest) where T : class
        {
            TValue value = expression.Compile()(objectToTest);
            var memberAccessor = new MemberAccessor<T, TValue>(expression, true);
            TestValidationResult<T> testValidationResult = validator.TestValidate(objectToTest, opt => opt.IncludeProperties(memberAccessor.Member.Name));
            return testValidationResult.ShouldHaveValidationErrorFor(expression);
        }

        public static void ShouldNotHaveValidationErrorForExact<T, TValue>(this IValidator<T> validator,
            Expression<Func<T, TValue>> expression, TValue value) where T : class, new()
        {

            var instanceToValidate = new T();

            var memberAccessor = new MemberAccessor<T, TValue>(expression, true);
            memberAccessor.Set(instanceToValidate, value);

            TestValidationResult<T> testValidationResult = validator.TestValidate(instanceToValidate, opt => opt.IncludeProperties(memberAccessor.Member.Name));
            testValidationResult.ShouldNotHaveValidationErrorFor(expression);
        }

        public static void ShouldNotHaveValidationErrorForExact<T, TValue>(this IValidator<T> validator, Expression<Func<T, TValue>> expression, T objectToTest) where T : class
        {
            TValue value = expression.Compile()(objectToTest);
            var memberAccessor = new MemberAccessor<T, TValue>(expression, true);
            TestValidationResult<T> testValidationResult = validator.TestValidate(objectToTest, opt => opt.IncludeProperties(memberAccessor.Member.Name));
            testValidationResult.ShouldNotHaveValidationErrorFor(expression);
        }

Upvotes: 3

The validate method allows you to use options and specify which properties you want to check. (By default it include rules for all properties)

So for this here's an example (this code will validate rule only for StatudId)

_validator.Validate(command, options => 
{
  options.IncludeProperties(x => x.StatusId);
});

more details can be found on the official website here. https://docs.fluentvalidation.net/en/latest/start.html#throwing-exceptions

Upvotes: 4

Related Questions