TDD

.NET Musings

Wandering thoughts of a developer, architect, speaker, and trainer

NAVIGATION - SEARCH

Asserting Behavior with JustMock

In my last post, we discussed the basics of mocking and why it should be of interested to you as a developer.  Today, we take a deeper look into validating the behavior of our code with the capabilities of JustMock Free Edition.

Expanding on our SecurityHandler, we will add a requirement to reload the user’s shopping cart when they successfully log in to the system.  To do this, we need a ShoppingCartRepository which is being developed by another team member.  In keeping with our SOLID principles, we will inject this dependency into our class by interface.  The new code looks like this:

public class SecurityHandler
{
    private readonly ILoginService _service;
    private readonly IShoppingCartRepository _cartRepo;
    public Cart ShoppingCart { get; internal set; }
    public int UserID { get; internal set; }
    public SecurityHandler(ILoginService service, IShoppingCartRepository cartRepo)
    {
        _cartRepo = cartRepo;
        _service = service;
    }
    public bool LoginUser(string userName, string password)
    {
        UserID = _service.ValidateUser(userName, password);
        if (UserID != 0)
        {
            ShoppingCart = _cartRepo.LoadCart(UserID);
            return true;
        }
        return false;
    }
}

The class itself is still very simple.  The ShoppingCartRepository is injected in, and the LoadCart method is called if the user successfully logs into the system.  If the user is not successful, the repository isn’t used.

Asserting Execution Count

This is a classic example where behavior checking comes in handy.  If the user does log in, we need to verify that the method was called, and if the user does not log in, we need to verify that the method does not get called.  This prevents not only incorrectly loading the cart, but saves computing cycles, since we not calling into the database when we don’t need to.

When we make this change to the SecurityHandler, we get compilation errors in our two existing tests, since we are not passing the IShoppingCartRepository into the SecurityHandler. To correct this, we create another Strict Mock for the repo and pass it in.  We also need to arrange the behavior that we are expecting on the repo.  For the “happy” path test, we want assert that the LoadCart method is called and returns a cart.

There are several ways to do this with JustMock.  If we just want to make sure that the method is called, but we don’t care how many times it was called, we can use the extension method MustBeCalled as in the following test:

[Test]
public void Should_Populate_Cart_And_UserID_With_Valid_Username_And_Password()
{
    string userName = "Bob";
    string password = "Password";
    int userID = 5;
    Cart cart = new Cart();
    ILoginService service = Mock.Create<ILoginService>(Behavior.Strict);
    IShoppingCartRepository repo = 
        Mock.Create<IShoppingCartRepository>(Behavior.Strict);
    Mock.Arrange(() => service.ValidateUser(userName, password))
        .Returns(userID).MustBeCalled();
    Mock.Arrange(() => repo.LoadCart(userID))
        .Returns(cart).MustBeCalled();
    SecurityHandler handler = new SecurityHandler(service, repo);
    Assert.IsTrue(handler.LoginUser(userName, password));
    Assert.AreEqual(handler.UserID, userID);
    Assert.AreSame(handler.ShoppingCart, cart);
    Mock.Assert(service);
    Mock.Assert(repo);
}

There are several forms tat can be used to test for the execution behavior:

  • Occurs(numberOfTimes) – exact number of times a method must be called
  • OccursOnce() – same as Occurs(1)
  • OccursNever() – same as Occurs(0)
  • OccursAtLeast(numberOfTimes) – minimum number of times a method must be called
  • OccursAtMost(numberOfTimes) – maximum number of times a method can be called (no calls is acceptable)

I prefer using these forms for the added flexibility and readability of the tests.  The two pertinent lines from the previous test would look like this:

    Mock.Arrange(() => service.ValidateUser(userName, password))
        .Returns(userID).Occurs(1);
    Mock.Arrange(() => repo.LoadCart(userID))
        .Returns(cart).Occurs(1);

Another advantage of using Occurs(1) over MustBeCalled is the ability to check for redundant calls.  If the Handler was accidentally written like this (admittedly not the best code, but bear with me for a minute):

for (int x = 0; x < 10; x++)
{
      ShoppingCart = _cartRepo.LoadCart(UserID);
}

The test with MustBeCalled would pass, the test with Occurs(1) would fail.  The overall outcome of the code would still work.  It is calling into the datastore and creating an instance of the shopping cart.  Since it’s not additive, when it is done with the loop, you have a card, and presumably the correct cart. (The repository would have it’s own unit tests to make sure that it is working properly.)

I have run into this scenario more times than I would have thought through out my consulting career.  It’s never this obvious as my silly sample indicates, but I’ve been called in to fix web sites that were crashing because buried in a huge loop were some resource intensive calls.  And more often than not, the fix was to simply move them away from the loop.

Fine Tuning the Arrange

Working with Arguments

A detail that we’ve glossed over so far is the effect that parameters have on the arrangements.  In all of our examples so far, the arrange steps have been very straightforward. The parameters in the arrange step  matched the parameters where the system under test was executed.  But what if they didn’t?  What would happen?  As the tests are currently written, they would fail. 

JustMock provides several options for the arrange step to handle argument parameters. The simplest is to add the IgnoreArguments method to the arrange.  The following test shows an example of this.  Regardless of the parameter values, the call to the GetSum method returns 4.

[Test]
public void Show_IgnoreArgument_Option()
{
    var service = Mock.Create<IService>(Behavior.Strict);
    Mock.Arrange(() => service.GetSum(0, 0)).IgnoreArguments().Returns(7);
    Assert.AreEqual(7, service.GetSum(3, 4));
}

To illustrate this further, consider this test:

[Test]
public void Show_IgnoreArgument_Option()
{
    var service = Mock.Create<IService>();
    Mock.Arrange(() => service.GetSum(3, 4)).Returns(7);
    Assert.AreEqual(7, service.GetSum(3, 4));
    Assert.AreEqual(0, service.GetSum(4, 2));
}

For this test, we switch to a Loose Mock to show the effects of calling a method on a mock that has not been explicitly arranged.  In our arrange, we are stating “Any time the GetSum method on the mock is called with the parameters 3 and 4, return 7.” Any other call on the method will return the default value for the return type, which in this case, is zero.

We also have the ability to fine tune the arrange.  Instead of ignoring all arguments, we can selective handle the arguments of a test.

[Test]
public void Show_IngoreSingleArgument_Option()
{
    var service = Mock.Create<IService>();
    Mock.Arrange(() => service.GetSum(Arg.AnyInt, 4)).Returns(7);
    Assert.AreEqual(0, service.GetSum(3, 0));
    Assert.AreEqual(7, service.GetSum(3, 4));
}

In the test above, the arrange is stating “Any time the GetSum method on the mock is called with any value for the first parameter and 4 as the second parameter, return 7.”  The test shows that the first assert calls GetSum without passing a 4 in as the second parameter, and therefore is not an arranged call.  The return is zero.  The second call follows the rules of the arrange, and returns 7.

The Arg class supports all of the common data types, but if that isn’t enough, there is also a generic version of this call. Here is the same test, but instead of using Arg.AnyInt, we are using Arg.IsAny<int>().  This is obviously a trivial example, but illustrates how you can the pass in your own class into the arrange to give you the complete flexibility to set up the arrange in whatever way you need to.

[Test]
public void Show_IngoreSingleArgument_Option()
{
    var service = Mock.Create<IService>();
    Mock.Arrange(() => service.GetSum(Arg.IsAny<int>(), 4)).Returns(7);
    Assert.AreEqual(0, service.GetSum(3, 0));
    Assert.AreEqual(7, service.GetSum(3, 4));
}

As a final benefit option, JustMock provides the ability to setup the arrange with two additional Matchers:

  • Arg.IsInRange(int from, int to, RangeKind range) where RangeKind is Inclusive or Exclusive
  • Arg.Matches<T>(Expression<Predicate<T>> expression)

To demonstrate the second matcher, consider the following test:

[Test]
public void Show_Selective_Arrange_Option()
{
    var service = Mock.Create<IService>();
    Mock.Arrange(() => service.GetSum(Arg.AnyInt, Arg.AnyInt)).Returns(4);
    Mock.Arrange(() => service.GetSum(Arg.AnyInt, Arg.Matches<int>(x=> x<5 && x>0)))
        .Returns(8); 
    Assert.AreEqual(4, service.GetSum(3, 10));
    Assert.AreEqual(8, service.GetSum(3, 2));
}

In the second arrange, we use an expression to define a range of values.  If GetSum is called with the values 1 through 4, the mock will return 8. Otherwise it will return 4. It is also important to note that the arranges need to be ordered from most generic to most specialized.

Summary

Asserting behavior is often overlooked when developers are testing their code, but automating the testing of code interaction is an extremely valuable exercise. 

Typically, in greenfield development (aka new code), you would seldom have a need to place multiple arranges on the same mock in a single test (some would call that an anti-pattern).  However, if you are in a brownfield development (aka legacy code) situation, you don’t always have the luxury of making sure the code you are testing is following the SOLID principles, or even DRY (don’t repeat yourself).  The argument handlers (especially the Matches version) gives you the flexibility to handle just about any situation that crops up as you are trying to automate the testing of your application.

Happy Coding!

Managed Windows Shared Hosting by OrcsWeb