Rhino.Mocks has a great syntax for setting argument constraints since version 3.5 using inline constrains (you can read more about it here).
I recently came across two interesting cases that I think are worth sharing.
While the Arg<T> class provides a more elegant syntax, it sometimes leads to code such as this:
1: [Test]
2: public void Test()
3: {
4: var stub = MockRepository.GenerateStub<IDoSomething>();
5: stub.Stub(s => s.DoSomething(Arg<IList<ICustomer>>.Matches(list=>list[1].FirstName.Equals("Mike") && list[1].Age == 20);)).Return(1);
6:
7: var youngerMike = MockRepository.GenerateStub<ICustomer>();
8: youngerMike.Stub(s => s.Age).Return(20);
9: youngerMike.Stub(s => s.FirstName).Return("Mike");
10:
11: var olderMike = MockRepository.GenerateStub<ICustomer>();
12: olderMike.Stub(s => s.Age).Return(40);
13: olderMike.Stub(s => s.FirstName).Return("Mike");
14:
15: var customers = new[] { olderMike, youngerMike };
16: var result = stub.DoSomething(customers);
17:
18: Assert.That(result, Is.EqualTo(1));
19: }
That whole “Arg<IList<ICustomer>>.Matches” thing? Not a pretty sight, and that’s without having multiple parameters with multiple constraints…
Since Arg<T> returns a T, I figured I can use a local variable to hold the constraint, like this:
1: var listWithYoungerMikeAsSecond = Arg<IList<ICustomer>>.Matches(list=>list[1].FirstName.Equals("Mike") && list[1].Age == 20);
2: stub.Stub(s => s.DoSomething(listWithYoungerMikeAsSecond)).Return(1);
Still works and the tests is a little clearer (The code snippet might look as messy in this post, but it makes the difference inside VS).
So, what’s the problem?
These are the two examples that got me, well, a little frustrated. In the first, I grouped similar code lines together, since I like the pretty colors:
1: var youngerMikeStub = MockRepository.GenerateStub<IDoSomething>();
2: var olderMikeStub = MockRepository.GenerateStub<IDoSomething>();
3:
4: var listWithYoungerMikeAsSecond = Arg<IList<ICustomer>>.Matches(list => list[1].FirstName.Equals("Mike") && list[1].Age == 20);
5: var listWithOlderMikeAsSecond = Arg<IList<ICustomer>>.Matches(list => list[1].FirstName.Equals("Mike") && list[1].Age == 40);
6:
7: youngerMikeStub.Stub(s => s.DoSomething(listWithYoungerMikeAsSecond)).Return(1);
8: olderMikeStub.Stub(s => s.DoSomething(listWithYoungerMikeAsSecond)).Return(1);
Running this test throws the following exception at line 7 in the above:
System.InvalidOperationException: Use Arg<T> ONLY within a mock method call while recording. 1 arguments expected, 2 have been defined.
This was cause because we defined two Arg<T> constraints and actually using only one of them for the first stub method call!
The second example is this:
1: [Test]
2: public void Test()
3: {
4: var listWithYoungerMikeAsSecond = Arg<IList<ICustomer>>.Matches(list=>list[1].FirstName.Equals("Mike") && list[1].Age == 20);
5:
6: var stub = MockRepository.GenerateStub<IDoSomething>();
7: stub.Stub(s => s.DoSomething(listWithYoungerMikeAsSecond)).Return(1);
8:
9: var youngerMike = MockRepository.GenerateStub<ICustomer>();
10: youngerMike.Stub(s => s.Age).Return(20);
11: youngerMike.Stub(s => s.FirstName).Return("Mike");
12:
13: var olderMike = MockRepository.GenerateStub<ICustomer>();
14: olderMike.Stub(s => s.Age).Return(40);
15: olderMike.Stub(s => s.FirstName).Return("Mike");
16:
17: var customers = new[] { olderMike, youngerMike };
18: var result = stub.DoSomething(customers);
19:
20: Assert.That(result, Is.EqualTo(1));
21: }
And the tests fails since the method returns 0 and not 1 - I declared the Arg before the mock itself, now running this test doesn’t match our expectation although they’re clearly met.
Inline is INLINE!
Yes, I know they’re called INLINE constraints, but still, it took me a while to actually understand that Arg<T> isn’t just generating constraints, it’s actually coupled to the current mocked object.
What does it mean? It means that we should use Arg<T> inside the Stub method itself or right before it, but ONLY after generating the stub/mock and ONLY the specific Args we’re using in the following Stub method.
Mock away.