2

I am trying to understand why my validation test is failing, when I expect it to pass.

Here is the code:

using FluentValidation;

namespace FluentValidationIssue
{
    public class Model
    {
        public string Ean { get; set; }
        internal List<string>? ProvidedEans { get; private set; }
        internal string? SelectedSourceSetId { get; set; }
        internal string? SetName { get; set; }
    }

    public class ModelValidator : AbstractValidator<Model>
    {
        public ModelValidator()
        {
            RuleFor(model => model.SetName).NotEmpty();

            // nothing provided
            RuleFor(model => model.SelectedSourceSetId)
                .Empty()
            .When(model => model.ProvidedEans is null || model.ProvidedEans!.Count == 0)
            .WithMessage("Either an existing override/ymal set or a list of Eans must be provided.");

            // both an existing and a list provided.
            RuleFor(model => model.SelectedSourceSetId)
                .NotEmpty()
                .When(model => model?.ProvidedEans?.Count > 0)
                .WithMessage("Choose either an existing override/ymal set or provide a list of Eans, not both.");
        }
    }

    /// <summary> A program. </summary>
    internal class Program
    {
        /// <summary> Main entry-point for this application. </summary>
        static void Main(string[] args)
        {
            var model = new Model
            {
                Ean = "9780316587266",
                WorkId = 1234,
                SetName = "Set Name 1"
                SelectedSourceSetId = "a59ea6d32f6444c8afef284e557ab319"
            };

            var validator = new ModelValidator();
            var result = validator.Validate(model);
            if (result.IsValid)
            {
                Console.WriteLine("The model is valid.");
            }
            else
            {
                Console.WriteLine("The model is invalid.");
                foreach (var error in result.Errors)
                {
                    Console.WriteLine($"- {error.PropertyName}: {error.ErrorMessage}");
                }
            }
        }
    }
}

Given that I populated the model.SelectedSourceSetId with a string value, I expect the first rule to pass. Except it is failing and returns my custom message.

If I leave model.SelectedSourceSetId set to null, the first rule passes, and it should fail.

If I provide values for both SelectedSourceSetId and ProvidedEans, the second rule passes, when it should fail.

I am trying to do a cross field check. The model needs to have either a SelectedSourceSetId or a list of strings (ProvidedEans), but not both.

The rules are:

  1. If SelectedSourceSetId is an empty string/null and the ProvidedEans is null or ProvidedEans is an empty collection (Count == 0), than the error message should be triggered.

  2. If SelectedSourceSetId is an not empty/null and the ProvidedEans contains at least one item, than the error message should be triggered.

4
  • This might be silly, but does it affect anything if you make the internal properties public? I've looked at your code a few times and that's the only notable difference. Commented May 8 at 20:02
  • Or try moving the When(...) call to the last line in each rule? - RuleFor().NotEmpty().WithMessage().When() Commented May 8 at 20:03
  • And the program in your question is providing an Ean but not any ProvidedEans --- is that the bug? Commented May 8 at 20:05
  • @GregBurghardt, (a) internal -> public - no difference (b) .WithMessage() before .When() - no difference (c) no providing an EAN is not an issue. (d) Tried making the SelectedSourceSetId and ProvidedEans not nullable - no difference. This is validation against a data entry form. I just extracted some of the code. The full rule set doesn't validate EAN or WorkId. I know I can do what I want with a custom rule. I just don't understand why this doesn't work. Commented May 8 at 21:46

1 Answer 1

3

Given that I populated the model.SelectedSourceSetId with a string value, I expect the first rule to pass.

Your statement contradicts your code:

RuleFor(model => model.SelectedSourceSetId)
    .Empty()

This rule will never pass if model.SelectedSourceSetId is populated. That's the expected behavior of the Empty() validator.

In your description, you use the word "empty", to describe the error condition:

The rules are:

  • If SelectedSourceSetId is an empty string/null and the ProvidedEans is null or ProvidedEans is an empty collection (Count == 0), than the error message should be triggered.

But the Empty() validator is expressing the pass condition, so you have to invert it, to match your description: use NotEmpty() in your first rule and Empty() in your second.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.