Background

Mocking is all about making it easy to isolate the system under test in unit tests.  In non profiled mocking, this is typically done by creating a proxy for an interface, and arranging the methods that will be called on the proxy.  Mocking frameworks like JustMock determine matching calls not only based on the method signature, but also by the values of the arguments passed into the method.  JustMock provides many mechanism to tune how the matching is done.

The Interface Being Mocked

All of the tests in this post use the interface shown in Listing 1.

public interface IFoo
{
 int Add(int addend1, int addend2);
 string GetName(string firstName, string lastName);
}
Listing 1 – The Interface being mocked

Matching Arrangements

When a mock has a method called by the system under test, JustMock first checks for an Arrange statement on that method with a matching signature.  Next, JustMock checks to see if the values of the arguments passed in by the system under test to the mock’s method match the values used in the arrangement.  If they don’t, JustMock checks the matching conditions set up in the arrange.  If everything lines up, JustMock returns the value specified in the arrange statement (if the method has a return type).  If any of these conditions fail, then the method is considered unarranged by JustMock.  The behavior of JustMock then depends on whether the mock is a Loose mock or a Strict mock, as covered in the next section.

Strict and Loose Mocks

By default, JustMock creates Loose mocks.  By specifying Behavior.Strict in the creation of the mock, a Strict mock is created instead.  The biggest difference between the two is JustMock’s response to unmatched arrangements.  Loose mocks allow unmatched methods to execute, returning the default value for the return type if the method has a return type, or just silently doing nothing if the method is declared as void.  Strict mocks will throw an exception, thereby failing the test. The differences are illustrated in the two tests shown in Listing 2.

[Test]
public void ShouldReturnDefaultValueInLooseMock()
{
    var looseMock = Mock.Create<IFoo>();
    looseMock.Arrange(x => x.Add(3, 4)).Returns(7);
    looseMock.Add(3, 4).ShouldEqual(7);
    looseMock.Add(1, 4).ShouldEqual(0);
}
[Test]
public void ShouldThrowExceptionInStrictMock()
{
    var strictMock = Mock.Create<IFoo>(Behavior.Strict);
    strictMock.Arrange(x => x.Add(3, 4)).Returns(7);
    strictMock.Add(3, 4).ShouldEqual(7);
    Assert.Throws<MockException>(()=>strictMock.Add(1, 4));
}

Listing 2 – Loose vs. Strict Mocks

The same test, the only difference is the type of mock created.  The unarranged call  - Add(1,4) - in the first test returns zero, the default value for an integer.  In the second test, the same unarranged call on the Strict mock throws an exception. 

I usually use Loose mocks (the default) in my tests, but it’s nice to have the option of creating Strict mocks if the situation calls for it.

Ignoring Arguments

In the previous tests, the values for the arguments affected whether the arrange was matched.  This helps to create very focused arrangements based on what the test requires.  There are other times when the values don’t matter. JustMock provides several mechanisms to ignore the values of the arguments.

Ignoring All Arguments

The simplest form is to ignore all arguments.  This is accomplished by adding the .IgnoreArguments() method to the arrange, as shown in Listing 3.  Now all executions of the Add method, regardless of the values passed in, return 7.

[Test]
public void ShouldIgnoreAllArguments()
{
    var looseMock = Mock.Create<IFoo>();
    looseMock.Arrange(x => x.Add(3, 4)).IgnoreArguments().Returns(7);
    looseMock.Add(3, 4).ShouldEqual(7);
    looseMock.Add(1, 4).ShouldEqual(7);
}

Listing 3 – Ignoring All Arguments

Selectively Ignoring Arguments

In addition to ignoring all arguments, individual arguments can be ignored as well.  This is done by specifying that the argument can be anything of a certain type (including object).  There are two mechanisms for doing this using the Telerik.JustMock.Arg class.  If you want to assign the type of argument using generics, use the static method IsAny<>() (as shown in the uncommented arrangement in Listing 4).  There are also convenience static properties on the Arg class such as AnyInt, AnyString, etc. An example of this is shown in the commented out arrangement.

In the following test, any call to Add with a value of 3 as the first argument (regardless of the value of the second argument) returns 7.  Any other value passed in for the first argument results in an unmatched arrangement, and the Loose mock defaults to return zero (the default value for integers).

[Test]
public void Should_Ignore_Single_Argument()
{
    var looseMock = Mock.Create<IFoo>();
    looseMock.Arrange(x => x.Add(3, Arg.IsAny<int>())).Returns(7);
 //looseMock.Arrange(x => x.Add(3, Arg.AnyInt)).Returns(7); 
    looseMock.Add(3, 4).ShouldEqual(7);
    looseMock.Add(3, 7).ShouldEqual(7);
    looseMock.Add(1, 4).ShouldEqual(0);
}

Listing 4 – Ignoring a Single Argument

Argument Ranges

For tests where more flexibility is needed, JustMock also has a mechanism for specifying a range of values for an argument.  The call is generic, adding to the flexibility of the range matcher.  In addition to specifying the range, the type of matching (Inclusive or Exclusive) must be specified as well.  In the first test in Listing 5, the arrangement is matched if the value for the first parameter is 2 and the value for the second parameter is 0,1,2,3,4,5.  The second test in the listing demonstrates string matching.  Note that the matching is based on the first character of the string, and since the RangeKind.Exclusive is used, the values for the parameters must start with “B”,”C”, or “D”.

[Test]
public void ShouldMatchArgumentRange()
{
    var foo = Mock.Create<IFoo>();
    foo.Arrange(x => x.Add(2, Arg.IsInRange<int>(0, 5, RangeKind.Inclusive))).Returns(4);
    foo.Add(2, 3).ShouldEqual(4);
    foo.Add(2, 6).ShouldEqual(0);
}
[Test]
public void ShouldMatchStringArgumentRange()
{
    var foo = Mock.Create<IFoo>();
    foo.Arrange(x => x.GetName("Phil", Arg.IsInRange<string>("A", "E", RangeKind.Exclusive)))
        .Returns("Phil J");
    foo.GetName("Phil", "B").ShouldEqual("Phil J");
    foo.GetName("Phil", "BCDGR").ShouldEqual("Phil J");
    foo.GetName("Phil", "A").ShouldEqual(null);
}

Listing 5 – Using Ranged Matchers

Using Lambdas for Argument Matching

The final matching mechanism for argument values allows specification of the valid values through lambda expressions. This adds a considerably flexible option for any matching conditions not already handled by the methods already listed here.  The syntax of this matching option is Arg.Matches<T>(Expression<Predicate<T>> expression).

The code in Listing 6 demonstrates this through a straight lambda (the first test) and by calling into another function (the second test).  The ability to call into a method to determine a match is extremely powerful, but should be used wisely.  While abstracting code into methods in line of business code is extremely beneficial, obscuring the logic in a test can make the tests hard to read.  I prefer to stick with the straight lambda expression syntax.

[Test]
public void Should_Match_Argument_Lambda()
{
    var foo = Mock.Create<IFoo>();
    foo.Arrange(x => x.Add(2, Arg.Matches<int>(y => y < 10)))
        .Returns(10);
    foo.Add(2, 9).ShouldEqual(10);
    foo.Add(2, 11).ShouldEqual(0);
}
 
[Test]
public void Should_Match_Argument_Predicate()
{
    var foo = Mock.Create<IFoo>();
    foo.Arrange(x => x.Add(2, Arg.Matches<int>(y => CheckInput(y))))
        .Returns(10);
    foo.Add(2, 9).ShouldEqual(10);
    foo.Add(2, 11).ShouldEqual(0);
}
Listing 6 – Lambda in Matchers

Summary

JustMock brings a considerable amount of customization to the matching process for arranged method calls on mocks.  With the range of options available, unit tests can be honed to a razor’s edge to make sure the tests execute exactly as planned.

JustMock banner
Japikse
About the Author

Phil Japikse

is an international speaker, a Microsoft MVP, ASPInsider, INETA Community Champion, MCSD, CSM/ CSP, and a passionate member of the developer community. Phil has been working with .Net since the first betas, developing software for over 20 years, and heavily involved in the agile community since 2005. Phil also hosts the Hallway Conversations podcast (www.hallwayconversations.com) and serves as the Lead Director for the Cincinnati .Net User’s Group (http://www.cinnug.org). You can follow Phil on twitter via www.twitter.com/skimedic, or read his personal blog at www.skimedic.com/blog.

 

Related Posts

Comments

Comments are disabled in preview mode.