Marco
Marco

Reputation: 23935

How do I mock an AbstractValidator that is using rule sets?

I have an abstract validator, with the following structure:

public abstract class RiskAssessmentServiceCreateRequestValidator<T>
    : AbstractValidator<T> where T
        : IRiskAssessmentServiceCreateRequest
{
    public RiskAssessmentServiceCreateRequestValidator(ApplicationContext context)
    {
        RuleSet("modelBinding", () =>
        {
            RuleFor(x => x.ServiceProviderId).NotNull().GreaterThan(0);
        });

        RuleSet("handler", () =>
        {
            //....
        });

    }
}

In my request handler I am calling a derived instance of this class like that:

var validationResult = _validator.Validate(request, ruleSet: "handler");

How can I mock that particular call to Validate in my unit tests? If I would not use the rule sets, my Setup would look like this:

_validator.Setup(x => x.Validate(It.IsAny<CreateRequest>()))
          .Returns(validationResult);

The following call is not allowed, since optional parameters are not allowed in an expression tree:

_validator.Setup(x => x.Validate(
                It.IsAny<CreateRequest>(), 
                ruleSet: It.IsAny<string>()))
          .Returns(validationResult);

Theoretically I could set it up like this:

_validator.Setup(x => x.Validate(
                It.IsAny<CreateRequest>(), 
                (IValidatorSelector)null,
                It.IsAny<string>()))
           .Returns(validationResult);

But this then results in:

System.NotSupportedException : Unsupported expression: x => x.Validate<CreateRequest>(It.IsAny<CreateRequest>(), null, It.IsAny<string>())
    Extension methods (here: DefaultValidatorExtensions.Validate) may not be used in setup / verification expressions.

Except from using the real validator, which I want to avoid, how can I resolve this and setup Moq in a suitable way?

Upvotes: 3

Views: 4485

Answers (2)

DavidG
DavidG

Reputation: 119016

There are really two questions here.

  1. The first is how to mock with optional parameters - Simply treat optional parameters are non-optional.

  2. However, you are trying to mock an extension method, that is not possible. Instead, you need to mock the method that the extension is trying to call. A cursory glance at the source, and I think that under the hood it is calling validator.Validate(ValidationContext) so your Moq code could be something like this:

    _validator
        .Setup(x => x.Validate(It.IsAny<ValidationContext<CreateRequest>>())
        .Returns(validationResult);
    

Upvotes: 9

weichch
weichch

Reputation: 10045

Try

var mock = new Mock<AbstractValidator<object>>();
mock.Setup(x => x.Validate(It.Is<ValidationContext<object>>(ctx => IsExpectedRuleSet(ctx, new[] { "Rule1", "Rule2" }))))
    .Return(...);

mock.Object.Validate(new object(), ruleSet: "Rule1,Rule2");

bool IsExpectedRuleSet(ValidationContext context, string[] expectedRuleSet)
{
    return (context.Selector as FluentValidation.Internal.RulesetValidatorSelector)?.RuleSets.SequenceEqual(expectedRuleSet) == true;
}

Upvotes: 0

Related Questions