Matchers let you ignore passing actual values as arguments used in mocks. Instead, they give you the possibility to pass just an expression that satisfies the argument type or the expected value range. For example, if a method accepts a string as a first parameter, you don’t need to pass a specific string like "Camera". Instead, you can use Arg.IsAny<string>(). There are 3 types of matchers supported in Telerik JustMock:
- Arg.IsAny<[Type]>();
-
Arg.IsInRange([FromValue : int], [ToValue : int], [RangeKind])
- Arg.Matches<T>(Expression<Predicate<T>> expression)
Matchers also let you ignore all arguments in your mocks by a single call to Args.Ignore().
Let's take a look at each one of these features in details.
Arg.IsAny<T>();
We've already used this matcher in one of our examples earlier.
| C# | Copy |
|---|
Mock.Arrange(() => warehouse.HasInventory(Arg.IsAny<string>(), Arg.IsAny<int>())).Returns(true);
|
| Visual Basic | Copy |
|---|
Mock.Arrange(Function() warehouse.HasInventory(Arg.IsAny(Of String)(), Arg.IsAny(Of Integer)())).Returns(True)
|
This matcher specifies that when the HasInventory method is called with any string as a first argument and any int as a second it should return true.
Arg.IsInRange(int from, int to, RangeKind range)
The IsInRange matcher lets us arrange a call for an expected value range. With the RangeKind argument we can specify whether the given range includes or excludes its boundaries.
Consider the following example:
| C# | Copy |
|---|
Mock.Arrange(() => foo.Echo(Arg.IsInRange(0, 5, RangeKind.Inclusive))).Returns(true);
|
| Visual Basic | Copy |
|---|
Mock.Arrange(Function() foo.Echo(Arg.IsInRange(0, 5, RangeKind.Inclusive))).Returns(True)
|
With the first line we specify that when the foo.Echo method is called with an argument value ranging from 0 to 5, the method will return true. We specify the RangeKind to be Inclusive which means that calling the method with 0 or 5 satisfies the range condition and it will return true for these values.
| C# | Copy |
|---|
Mock.Arrange(() => foo.Echo(Arg.IsInRange(0, 5, RangeKind.Exclusive))).Returns(true);
|
| Visual Basic | Copy |
|---|
Mock.Arrange(Function() foo.Echo(Arg.IsInRange(0, 5, RangeKind.Exclusive))).Returns(True)
|
On the second line we specify the RangeKind to be Exclusive so calling the method with 6 or 10 doesn’t satisfy the condition because these values are excluded from the range.
Arg.Matches<T> (Expression<Predicate<T>> expression)
This is the most flexible matcher and it allows you to specify your own matching expression. Let's illustrate it with a simple example:
| C# | Copy |
|---|
Mock.Arrange(() => foo.Echo(Arg.Matches<int>( x => x < 10)).Returns(true);
|
| Visual Basic | Copy |
|---|
Mock.Arrange(Function() foo.Echo(Arg.Matches(Of Integer)(Function(x) x < 10))).Returns(True)
|
With our expression (or predicate) x => x < 10 we specify that a call to foo.Echo with an argument less than 10 should return true.
Using Matchers in Assert
Matchers are also useful in assertion. Consider a fictitious payment service where we don't care what the payment date is, but we want to make sure that the payment amount which is passed to the service is $54.44.
We can easily achieve this by using the following statement:
CopyC#
[TestMethod]
public void ShouldUseMatchersInAssert()
{
var paymentService = Mock.Create<IPaymentService>();
paymentService.ProcessPayment(DateTime.Now, 54.44M);
Mock.Assert(() => paymentService.ProcessPayment(
Arg.IsAny<DateTime>(),
Arg.Matches<decimal>(paymentAmount => paymentAmount == 54.44M)));
}
CopyVB
<TestMethod()>
Public Sub ShouldUseMatchersInAssert()
Dim paymentService = Mock.Create(Of IPaymentService)()
paymentService.ProcessPayment(DateTime.Now, 54.44D)
Mock.Assert(Sub() paymentService.ProcessPayment(
Arg.IsAny(Of DateTime)(),
Arg.Matches(Of Decimal)(Function(paymentAmount) paymentAmount = 54.44D)))
End SubWe assert for calling ProcessPayment with whatever DateTime argument and payment amount exactly $54.44.
Here is another example. We specify that an Echo call with arguments 10 and 20 should return 30.
We use the matchers Arg.Matches<int>(x => x == 10) and Arg.Matches<int>(x => x == 20)
to handle the case when we pass 10 and 20 to the Echo method.
CopyC#
[TestMethod]
public void ShouldUseMatchersInArrange()
{
var foo = Mock.Create<IFoo>();
Mock.Arrange(() => foo.Echo(Arg.Matches<int>(x => x == 10), Arg.Matches<int>(x => x == 20))).Returns(30);
int ret = foo.Echo(10, 20);
Assert.AreEqual(30, ret);
}
CopyVB
<TestMethod()>
Public Sub ShouldUseMatchersInArrange()
Dim foo = Mock.Create(Of IFoo)()
Mock.Arrange(Function() foo.Echo(Arg.Matches(Of Integer)(Function(x) x = 10), Arg.Matches(Of Integer)(Function(x) x = 20))).Returns(30)
Dim ret As Integer = foo.Echo(10, 20)
Assert.AreEqual(30, ret)
End Sub
Using Matchers to ignore all arguments in Assert
You already saw how you can use matchers in your assertion calls. If you need you can specify that you want to ignore all arguments in the assert call. You do this by adding the Args.Ignore() argument to the Mock.Assert call. Here is an example:
CopyC#
[TestMethod]
public void UsingMatchersInAssertExample2()
{
var paymentService = Mock.Create<IPaymentService>();
paymentService.ProcessPayment(DateTime.Now, 54.44M);
Mock.Assert(() => paymentService.ProcessPayment(new DateTime(), 0), Args.Ignore());
}
CopyVB
<TestMethod()>
Public Sub ShouldIgnoreArgumentsInAssert()
Dim paymentService = Mock.Create(Of IPaymentService)()
paymentService.ProcessPayment(DateTime.Now, 54.44D)
Mock.Assert(Sub() paymentService.ProcessPayment(New DateTime(), 0), Args.Ignore())
End SubIn this way we assert for calling ProcessPayment no matter the arguments.
Using Matchers and Specializations
In an arrangement you can define more than one matcher. Consider the following example:
CopyC#
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void UsingMatchersAndSpecializations()
{
var foo = Mock.Create<IFoo>();
Mock.Arrange(() => foo.Echo(Arg.AnyInt)).Returns(10);
Mock.Arrange(() => foo.Echo(Arg.Matches<int>(x => x > 10))).Throws(new ArgumentException());
int actual = foo.Echo(1);
Assert.AreEqual(10, actual);
foo.Echo(11);
}
CopyVB
<TestMethod()> _
<ExpectedException(GetType(ArgumentException))> _
Public Sub ShouldUseMatchersAndSpecializations()
Dim foo = Mock.Create(Of IFoo)()
Mock.Arrange(Function() foo.Echo(Arg.AnyInt)).Returns(10)
Mock.Arrange(Function() foo.Echo(Arg.Matches(Of Integer)(Function(x) x > 10))).Throws(New ArgumentException())
Dim actual As Integer = foo.Echo(1)
Assert.AreEqual(10, actual)
foo.Echo(11)
End Sub
In the case when a specialization is used among with Arg.IsAny<T>, JustMock will select the arrangement with the proper matcher.
foo.Echo(1) will use the first matcher and will return 10. But foo.Echo(11) will use the second
matcher because it is a specialization of the first and applies the call, therefore ArgumentException
will be thrown.
See Also