(Re)improved argument matchers in Moq
In the past I wrote about MatcherAttibute in Moq allows developers to create quite easy argument matchers. Due to a suggestion, community opinions and Kzu's implementation, the matchers were improved again.
In an scenario with a Customer class and a IFooService:
1: public class Customer
2: {
3: public string Name { get; set; }
4: public int Age { get; set; }
5: // ...
6: }
7:
8: public interface IFooService
9: {
10: void Bar(Customer c);
11: }
If in a test you need an expectation on Bar method that stands for a Customer c where c.Age >= 18 you
needed to do something like this:
1: [Test]
2: public void Test1()
3: {
4: var mock = new Mock<IFooService>(MockBehavior.Strict);
5: mock.Setup(x => x.Bar(It.Is<Customer>(c => c.Age >= 18)));
6: // this will success
7: // mock.Object.Bar(new Customer { Age = 21 });
8: // this will fail
9: // mock.Object.Bar(new Customer { Age = 17 });
10: }
but from now on, using the Matcher factory the same expectation could be written as follows:
1: [Test]
2: public void Test2()
3: {
4: var mock = new Mock<IFooService>(MockBehavior.Strict);
5: mock.Setup(x => x.Bar(GrownUp()));
6: // this will success
7: // mock.Object.Bar(new Customer { Age = 21 });
8: // this will fail
9: // mock.Object.Bar(new Customer { Age = 17 });
10: }
11:
12: public static Customer GrownUp()
13: {
14: return Match<Customer>.Create(c => c.Age >= 18);
15: }
There is some tricky stuff around type here (returning a Match<T> in a method with return type). If you want to see how this work go to the code and read this discussion. All in all is to have a good experience with the compiler.
Continue to read the post and you will see some other alternatives!
Use property as matchers
Last sample could be rewritten with less parenthesis using property getters.
1: [Test]
2: public void Test2()
3: {
4: var mock = new Mock<IFooService>(MockBehavior.Strict);
5: mock.Setup(x => x.Bar(GrownUp));
6: // this will success
7: // mock.Object.Bar(new Customer { Age = 21 });
8: // this will fail
9: // mock.Object.Bar(new Customer { Age = 17 });
10: }
11:
12: public static Customer GrownUp
13: {
14: get { return Match<Customer>.Create(c => c.Age >= 18); }
15: }
More arguments
Also you could have more arguments involved in the matching, just add them as method arguments and use them in the lambda expression.
For example to match older than a certain age:
1: [Test]
2: public void Test3()
3: {
4: var mock = new Mock<IFooService>(MockBehavior.Strict);
5: mock.Setup(x => x.Bar(OlderThan(18)));
6: // this will success
7: // mock.Object.Bar(new Customer { Age = 21 });
8: // this will fail
9: // mock.Object.Bar(new Customer { Age = 17 });
10: }
11:
12: public static Customer OlderThan(int minimumAge)
13: {
14: return Match<Customer>.Create(c => c.Age >= minimumAge);
15: }
Your own helper class
To reuse some matchers, you could define them in a separate a class instead of inside the fixture.
1: [Test]
2: public void Test4()
3: {
4: var mock = new Mock<IFooService>(MockBehavior.Strict);
5: mock.Setup(x => x.Bar(An.OlderThan(18)));
6: // this will success
7: // mock.Object.Bar(new Customer { Age = 21 });
8: // this will fail
9: // mock.Object.Bar(new Customer { Age = 17 });
10: }
11:
12: static class An
13: {
14: public static Customer OlderThan(int minimumAge)
15: {
16: return Match<Customer>.Create(c => c.Age >= minimumAge);
17: }
18: }
Non static method
Finally, the matchers declaration and implementation don't need to be static. So you could have additional context of matching.
Although I suggest this last only in certain scenarios, for example, match enumeration of values:
1: [Test]
2: public void Test5()
3: {
4: var mock = new Mock<IFooService>(MockBehavior.Strict);
5: var names = new E("John Doe", "Brian Cardiff");
6: mock.Setup(x => x.Bar(names.Some));
7:
8: // this will success
9: // mock.Object.Bar(new Customer { Name = "Brian Cardiff" });
10:
11: // this will fail
12: // mock.Object.Bar(new Customer { Name = "Sandy" });
13: }
14:
15: public class E
16: {
17: string[] names;
18: public E(params string[] names)
19: {
20: this.names = names;
21: }
22:
23: public Customer Some
24: {
25: get { return Match<Customer>.Create(s => names.Contains(s.Name)); }
26: }
27: }
More lines in matcher code
There is no need that the matcher code is a one-line lambda, in case you are not familiar writing lambdas, here is another sample:
1: [Test]
2: public void Test6()
3: {
4: var mock = new Mock<IFooService>(MockBehavior.Strict);
5: mock.Setup(x => x.Bar(Between(20, 30)));
6: // this will success
7: // mock.Object.Bar(new Customer { Age = 25 });
8: // this will fail
9: // mock.Object.Bar(new Customer { Age = 40 });
10: }
11:
12: private Customer Between(int from, int to)
13: {
14: return Match<Customer>.Create(c =>
15: {
16: if (c.Age < from)
17: return false;
18: if (c.Age > to)
19: return false;
20: return true;
21: });
22: }