Jul 22, 2010

Mocking Comparison – Part 4: Parameter Constraints

Continuing with our comparison of Rhino Mocks, Moq and NSubstitute we now turn our eye to how constraints are managed.

Consider a method with parameters that was want to mock.  By default, a mock object will only return a specified value if the parameters passed to it exactly match the call signature specified to the mock object.  Maybe that’s a bit wordy, so as a quick example if you say Method(“a”).Returns(“Fred”) you’ll only get “Fred” back from the mock when you pass “a” as the parameter.  Passing “b” gets you nothing. Make sense?

OK, so what if I then want my mock object to provide different return values when different parameter values are used.  How do we avoid writing too much code or having to work out exactly what we should expect as a parameter every time?  And what if I don’t care what’s passed in, I just want a return value.

This is where constraints come into play.  Let’s look at some code to show how it works:

Rhino Mocks

Here’s a test where we try to add fleas to our monkey.  Adding positive numbers of fleas should succeed and adding a negative number of fleas should fail.

[Fact]
public void Rhino_no_constraints()
{
var monkey = MockRepository.GenerateMock<IMonkey>();
monkey.Stub(m => m.TryAddFleas(5)).Return(true);
monkey.Stub(m => m.TryAddFleas(-1)).Return(false);
monkey.Stub(m => m.TryAddFleas(9)).Return(true);

Assert.Equal(true, monkey.TryAddFleas(5));
Assert.Equal(false, monkey.TryAddFleas(-1));
Assert.Equal(true, monkey.TryAddFleas(9));
}

Notice how the use of explicit parameter values in the stubs means we have to repeat a lot of code.  Hmm, that’s annoying.

Thankfully we can simplify this by just ignoring the arguments as seen in the code below.  Note that we still have to supply a parameter value for the TryAddFleas lambda in the Stub call, even though it’s ignored.

[Fact]
public void Rhino_ignore_arguments()
{
var monkey = MockRepository.GenerateMock<IMonkey>();
monkey.Stub(m => m.TryAddFleas(0)).IgnoreArguments().Return(true);

Assert.Equal(true, monkey.TryAddFleas(5));
Assert.Equal(false, monkey.TryAddFleas(-1));
Assert.Equal(true, monkey.TryAddFleas(9));
}

However this will now cause the test to fail because we no longer return a false when passed -1 as a value.

So what we really want is to only return true when the argument value is in a certain range.  For fun let’s make that range between 4 and 9 inclusive.  Here’s the test now, using constraints:

[Fact]
public void Rhino_constraints()
{
var monkey = MockRepository.GenerateMock<IMonkey>();

monkey.Stub(m => m.TryAddFleas(0))
.Constraints(Is.LessThan(10) && Is.GreaterThan(3))
.Return(true);
Assert.Equal(true, monkey.TryAddFleas(5));
Assert.Equal(false, monkey.TryAddFleas(-1));
Assert.Equal(true, monkey.TryAddFleas(9));
}

Whilst this means I have 2 less Stub calls to make, it does tend to be a little verbose.  Expressive, but verbose.  For those wondering why I don’t have a constraint for the -1 argument, I’m relying on the standard behaviour of mocks.  If a constraint isn’t matched then the standard mock behaviour is to return the default value of the return type, being false in this case.

Moq

Here’s the same thing in Moq.  For brevity and completeness I’ve included a commented out line that shows what you would do if you just wanted to ignore parameter values

[Fact]
public void Moq_constraints()
{
var monkey = new Mock<IMonkey>();

//monkey.Setup(m => m.TryAddFleas(It.IsAny<int>())).Returns(true);
monkey.Setup(m => m.TryAddFleas(It.IsInRange(3, 10, Range.Exclusive)))
.Returns(true);
Assert.Equal(true, monkey.Object.TryAddFleas(5));
Assert.Equal(false, monkey.Object.TryAddFleas(-1));
Assert.Equal(true, monkey.Object.TryAddFleas(9));
}

The IsInRange method is quite nice, and the range constraints can be either inclusive or exclusive, however the code feels as verbose if not more so than the Rhino approach.

Note that if you use a test harness like MSpec (Machine.Specifications) then the “It” static class that Moq uses clashes with the “It” class used by MSpec for defining specifications which makes writing code a little painful at times.

NSubstitute

Finally the NSubstitute version.

[Fact]
public void Nsubtitute_constraints()
{
var monkey = Substitute.For<IMonkey>();

//monkey.TryAddFleas(Arg.Any<int>()).Returns(true);
monkey.TryAddFleas(Arg.Is<int>(count => count > 3 && count < 10))
.Returns(true);

Assert.Equal(true, monkey.TryAddFleas(5));
Assert.Equal(false, monkey.TryAddFleas(-1));
Assert.Equal(true, monkey.TryAddFleas(9));
}

This works much the same as the others with the difference being that you have a predicate as the parameter and you need to supply the parameter type to the Arg.Is<T> call.

From a readability perspective, there’s less code which is good and it’s easier to read than the other frameworks because of it’s terseness.  As a bonus, using the Arg.Any<T> call means we can avoid lambdas completely..

 

Verdict: I’ll go with the NSubstitute version as my first choice, though it’s more a matter of style choice than anything else. After that I prefer the Moq syntax over Rhino in this case.

 

Other posts in this series:

3 comments:

  1. I know this is a contrived example for the purposes of demonstrating mocking, but to me you are testing the mocking framework rather than useing the mock object to test your code.

    I'd like it more if you used a better example, but I can't think of an example, so maybe I should keep my mouth shut :)

    ReplyDelete
  2. @Anonymous You're right in that it's testing the mock (which is not what to do).

    It's a contrived example purely for the purposes of showing how things work, not for how best to do things :-)

    I didn't use a more "real world" example because it adds noise to the code when I just wanted to talk about the syntax.

    ReplyDelete
  3. I think you missed that Moq can also use a predicate for the constraint:

    monkey.Setup(m => m.TryAddFleas(It.Is(count => count > 3 && count < 10))).Returns(true);

    moreover, the new linq to mocks syntax also works with constraints:

    var monkey = Mock.Of{IMonkey}(x => x.TryAddFleas(It.Is(count => count > 3 && count < 10)) == true);


    Great comparison so far, btw :)

    ReplyDelete