Dennis Burton's Develop Using .NET

Change is optional. Survival is not required.
Tags: patterns | programming | testing

The Scenario

On our continuing quest to create unit tests that exercise only the class under test, we look at another common scenario that occurs while writing tests. As with the Dummy, our class under test has a dependency on another class, only this time the dependent class has an active role in our test. Our testing needs are about the logic of the class under test and not the interaction with the dependent object. In order to create a good test, the class under test must be isolated from the dependent object.

An example of this scenario might look like this:

   11     public interface ICalculator

   12     {

   13         int Add(int left, int right);

   14     }

   15     public class Fib

   16     {

   17         private ICalculator _calculator;

   18         public ICalculator Calculator

   19         {

   20             get { return _calculator; }

   21             set { _calculator = value;    }

   22         }

   23 

   24         public int Next(int i, int j)

   25         { return Calculator.Add(i,j); }

   26     }

The class under test in this scenario is Fib, which has a dependency on an ICalculator. The test's objective is to validate that the Next method returns the correct result for some well-known examples.

The Vocabulary

The name of this pattern is the Stub. A stub stands in the place of the actual object in use and provides known answers and predictable behavior. If you are doing any sort of evolutionary development, chances are that the initial versions of your classes more resemble stubs then real code. Why? The goal is the same: you wrote stub functionality to allow you to focus your development efforts on different parts of the system. This is exactly what we are doing with Stub tests: isolate one part of the system from another by providing known results.

The solution without tools

Unlike with a Dummy, providing a class that throws a NotImplementedExcpetion in the Add method does not meet our needs. Since the functionality of ICalculator is outside the scope of this test, we assume that it is working correctly (and hopefully under test). A simplistic implementation of ICalculator will work nicely. Since we are not testing the calculator, provide a simplistic calculator that returns fixed results.

   38     [Test]

   39     public void NextResultIsCorrect()

   40     {

   41         Fib fib = new Fib();

   42         fib.Calculator = new StubCalculator();

   43 

   44         Assert.That(fib.Next(2, 3), Is.EqualTo(5));

   45     }

 

   29     public class StubCalculator : ICalculator

   30     {

   31         public int Add(int left, int right)

   32         { return 5; }

   33     }

The test will check that the Next method returns the correct result given the StubCalculator. What we end up doing here is fully exercising the Fib class with known values from its dependent classes. The stub gives us the proper level of isolation for this test.

The solution with Rhino Mocks

For this version, leverage Rhino Mocks to keep us from having to code physical versions of the Stub class. Using Rhino's Fluent Interface, this reads as Expect a call on Calculator with the parameters 2 and 3, and when making this call return 5 as a result.

   47     [Test]

   48     public void NextNumberIsCorrect()

   49     {

   50         Fib fib = new Fib();

   51         fib.Calculator = MockRepository.GenerateStub<ICalculator>();

   52         fib.Calculator.Expect(calc => calc.Add(2, 3)).Return(5);

   53 

   54         Assert.That(fib.Next(2, 3), Is.EqualTo(5));

   55     }

The tools advantage

Just as discussed with IComplicated in the Dummy sample, adding methods to ICalculator does not require any additional maintenance of this test. However, unlike the Dummy sample, calls into the stub return the expected value. Additional benefits can easily pile up. Consider adding multiple calls to Add with different parameters. The hand-coded version would need some sort of conditional logic to determine what to return based on the calling parameters. Complexity adds up fast, even in the simple example listed here. Using Rhino, one concise and readable line of code can use new parameters for an additional expectation, including the expected result, and Rhino deals with matching up the parameters with the correct result. This is just a glimpse into the functionality offered by Rhino; the upcoming patterns will cover even more capability.

Isolation effects

Should a test that checks the result of the Next method fail if there is something wrong with the ICalculator implementation? The answer, as always, is "it depends." This test should fail if we were writing an integration test—a test that ensures all the pieces of a system are working together. This test should not fail based on ICalculator if it is a unit test, focused only on the result of Next. Isolating ICalculator from Fib helps build a set of unit tests that can quickly identify the location of errors introduced into a system. The stub is a common pattern of isolation, and using it will make a marked and immediate improvement in your tests.

The Series

PatternsInTesting[4] - Mock Pattern
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview

Dennis Burton

View Dennis Burton's profile on LinkedIn
Follow me on twitter
Rate my presentations
Google Code repository

Community Events

Windows Azure Boot Camp Lansing GiveCamp