Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot mock some things becuase they are extension methods #191

Closed
sethreidnz opened this issue Jan 18, 2016 · 5 comments
Closed

Cannot mock some things becuase they are extension methods #191

sethreidnz opened this issue Jan 18, 2016 · 5 comments

Comments

@sethreidnz
Copy link

I notice that you do have a IValidator which is great because I can mock them in a unit test. But for some of the documented methods they are extension methods in DefaultValidatorExtensions.

Is there a particular reason for this? What I want to be able to do is test my business service to ensure the correct rule is being applied without acutally having to have the instance (just using a mock). For example using Moq and Xunit I have a test like this:

        [Fact]
        public void GetPurchasePrice_ReturnsErrorResult_NoValidPurchasePrice()
        {
            //arrange
            var portfolioProperty = new PortfolioPropertyModel()
            {
                PropertyDetail = new PropertyDetailModel
                {
                    LastPurchasePrice = -1M
                }
            };

            var validationResult = new ValidationResult(new List<ValidationFailure>
                                    {
                                        new ValidationFailure("Property","Error")
                                    });

            _portfolioPropertyCalculationPropertyValidator.Setup(pv =>
                    pv.Validate(It.Is<PortfolioPropertyModel>(v => v == portfolioProperty),
                                It.Is<string>(v => v == ValidationRuleSets.PortfolioPropertyModelForCalculationValidator.HAS_VALID_PURCHASE_PRICE)))
                                .Returns(validationResult);

            _calculationResultFactoryMock.Setup(tc =>
                    tc.CreateFromError<decimal>(It.Is<IList<string>>(v => v.First() == validationResult.GetErrorStrings().First())))
                    .Verifiable();

            IPortfolioPropertyCalculationEngine sut = new PortfolioPropertyCalculationEngine(_propertyCalculatorMock.Object,
                                                             _calculationResultFactoryMock.Object,
                                                             _loanCalculationEngine.Object,
                                                             _portfolioPropertyToWithCalculationsTranslator.Object,
                                                             _portfolioPropertyCalculationPropertyValidator.Object);

            //act
            var result = sut.GetPurchasePrice(portfolioProperty);

            //assert
            _calculationResultFactoryMock.VerifyAll();
            Assert.True(result.Error);
        }

This is not possible however since the overload with ruleSet parameter is a static extension method... Is there any reason why it couldn't be on the IValidator interface?

Incidentally that was the mistake I had make in the other issue I opened.. Not including FluentValidation namespace correctly.

@JeremySkinner
Copy link
Member

I really wouldn't recommend this approach.

Don't try and mock individual aspects of FV...either hide the entirety of FV
behind your own interface, or better still, have some test helpers for
building valid/invalid objects, so you don't have to mock FV at all, and let it run as normal.

Mocking in this ay tends to lead to very brittle tests, so I'd suggest avoiding it
unless absolutely necessary.

@sethreidnz
Copy link
Author

Thanks for that. I ended up going that way and just using the actual validator instances in the business logic that was using it.

Is that what you meant? OR am I misunderstanding your comment.

@antonovicha
Copy link

antonovicha commented Jun 16, 2016

Mocking in this ay tends to lead to very brittle tests

Could you please elaborate on this?

So far I can easily mock validators derived from AbstractValidator. As long as I am not using RuleSet's...

Unfortunately using real validators in unit tests is cumbersome due to fact that they do have additional dependencies that needs to me mocked.

@JeremySkinner
Copy link
Member

JeremySkinner commented Jun 16, 2016

Mocking is for testing how your code interacts with a particular contract or protocol.

As an example, if you have some code that interacts with a web service and you want to test how your code acts depends on different responses from this service, then you'd extract the true implementation of the web service API behind an interface (the contract), and supply a mock version that can supply different results in your test. This is an appropriate use of mocking as you're testing the interaction with a contract (the interface) not a concrete implementation.

Don't mock classes. A class is not a contract- it's an implementation. This means it's subject to change, you end up relying on internal behaviour and your tests become brittle.

As I mentioned above, I'd suggest using the real validator instances in your tests. If this really won't work for you then define an interface contract for validation, hide FluentValidation behind this and mock this in your tests. Or mock IValidator[T], but don't mock FV classes directly.

Edit: Please also note that the extension methods for rulesets are only wrappers for developer convenience. They are completely optional and you can use rulesets without these extension methods by just building up a ValidationContext and passing it to the Validate method: https://github.com/JeremySkinner/FluentValidation/blob/master/src/FluentValidation/DefaultValidatorExtensions.cs#L819

@antonovicha
Copy link

Don't mock classes.

I don't :). I am mocking IValidator. Which lack easy way of mocking RuleSet calls. Since they are dispatched via extension.

you can use rulesets without these extension methods by just building up a ValidationContext

I saw this way when I was digging into extension methods. I don't want to use this approach exactly because extension methods exists: it is requires more code to use it. And what is more important: mocking & putting expectation on IValidator.Validate(ValidationContext context) method is much more complicated.

At the moment I am using following mock when rule sets are used:

var validator = MockRepository.GenerateStrictMock<IValidator<IModel>>();
validator.Expect(b => b.Validate(Arg<ValidationContext>.Is.Anything))
    .Return(new ValidationResult());

However with this approach I am lacking check on rule name. I may mock it that way with rule name check:

validator.Expect(b => b.Validate(Arg<ValidationContext>.Is.Anything))
    .Callback((ValidationContext vc) => ((RulesetValidatorSelector)vc.Selector).RuleSets.Contains("Delete"))
    .Return(new ValidationResult());

But this is fall under 'do not depend on internal behavior of library'.

@lock lock bot locked and limited conversation to collaborators Aug 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants