- Test Double
- Test Stub
- Test Spy
- Mock Object
- Fake Object
- Configurable Test Double
- Hard-Coded Test Double
- Test-Specific Subclass
Hard-Coded Test Double
How do we tell a Test Double what to return or expect?
We build the Test Double by hard-coding the return values and/or expected calls.
Test Doubles are used for many reasons during the development of Fully Automated Tests. The behavior of the Test Double may vary from test to test, and there are many ways to define this behavior.
When the Test Double is very simple or very specific to a single test, the simplest solution is often to hard-code the behavior into the Test Double.
How It Works
The test automater hard-codes all of the Test Double’s behavior into the Test Double. For example, if the Test Double needs to return a value for a method call, the value is hard-coded into the return statement. If it needs to verify that a certain parameter had a specific value, the assertion is hard-coded with the value that is expected.
When to Use It
We typically use a Hard-Coded Test Double when the behavior of the Test Double is very simple or is very specific to a single test or Testcase Class. The Hard-Coded Test Double can be either a Test Stub , a Test Spy , or a Mock Object , depending on what we encode in the method(s) called by the SUT.
Because each Hard-Coded Test Double is purpose-built by hand, its construction may take more effort than using a third-party Configurable Test Double. It can also result in more test code to maintain and refactor as the SUT changes. If different tests require that the Test Double behave in different ways and the use of Hard-Coded Test Doubles results in too much Test Code Duplication , we should consider using a Configurable Test Double instead.
Implementation Notes
Hard-Coded Test Doubles are inherently Hand-Built Test Doubles (see Configurable Test Double) because there tends to be no point in generating Hard-Coded Test Doubles automatically. Hard-Coded Test Doubles can be implemented with dedicated classes, but they are most commonly used when the programming language supports blocks, closures, or inner classes. All of these language features help to avoid the file/class overhead associated with creating a Hard-Coded Test Double; they also keep the Hard-Coded Test Double’s behavior visible within the test that uses it. In some languages, this can make the tests a bit more difficult to read. This is especially true when we use anonymous inner classes, which require a lot of syntactic overhead to define the class in-line. In languages that support blocks directly, and in which developers are very familiar with their usage idioms, using Hard-Coded Test Doubles can actually make the tests easier to read.
There are many different ways to implement a Hard-Coded Test Double, each of which has its own advantages and disadvantages.
Variation: Test Double Class
We can implement the Hard-Coded Test Double as a class distinct from either the Testcase Class or the SUT. This allows the Hard-Coded Test Double to be reused by several Testcase Classes but may result in an Obscure Test (caused by a Mystery Guest) because it moves important indirect inputs or indirect outputs of the SUT out of the test to somewhere else, possibly out of sight of the test reader. Depending on how we implement the Test Double Class, it may also result in code proliferation and additional Test Double classes to maintain.
One way to ensure that the Test Double Class is type-compatible with the component it will replace is to make the Test Double Class a subclass of that component. We then override any methods whose behavior we want to change.
Variation: Test Double Subclass
We can also implement the Hard-Coded Test Double by subclassing the real DOC and overriding the behavior of the methods we expect the SUT to call as we exercise it. Unfortunately, this approach can have unpredictable consequences if the SUT calls other DOC methods that we have not overridden. It also ties our test code very closely to the implementation of the DOC and can result in Overspecified Software (see Fragile Test). Using a Test Double Subclass may be a reasonable option in very specific circumstances (e.g., while doing a spike or when it is the only option available to us), but this strategy isn’t recommended on a routine basis.
Variation: Self Shunt
We can implement the methods that we want the SUT to call on the Testcase Class and install the Testcase Object into the SUT as the Test Double to be used. This approach is called a Self Shunt.
The Self Shunt can be either a Test Stub, a Test Spy, or a Mock Object, depending on what the method called by the SUT does. In each case, it will need to access instance variables of the Testcase Class to know what to do or expect. In statically typed languages, the Testcase Class must also implement the interface on which the SUT depends.
We typically use a Self Shunt when we need a Hard-Coded Test Double that is very specific to a single Testcase Class. If only a single Test Method requires the Hard-Coded Test Double, using an Inner Test Double may result in greater clarity if our language supports it.
Variation: Inner Test Double
A popular way to implement a Hard-Coded Test Double is to code it as an anonymous inner class or block closure within the Test Method. This strategy gives the Test Double access to instance variables and constants of the Testcase Class and even the local variables of the Test Method, which can eliminate the need to configure the Test Double.
While the name of this variation is based on the name of the Java language construct of which it takes advantage, many programming languages have an equivalent mechanism for defining code to be run later using blocks or closures.
We typically use an Inner Test Double when we are building a Hard-Coded Test Double that is relatively simple and is used only within a single Test Method. Many people find the use of a Hard-Coded Test Double more intuitive than using a Self Shunt because they can see exactly what is going on within the Test Method. Readers who are unfamiliar with the syntax of anonymous inner classes or blocks may find the test difficult to understand, however.
Variation: Pseudo-Object
One challenge facing writers of Hard-Coded Test Doubles is that we must implement all the methods in the interface that the SUT might call. In statically typed languages such as Java and C#, we must at least implement all methods declared in the interface implied by the class or type associated with however we access the DOC. This often “forces” us to subclass from the real DOC to avoid providing dummy implementations for these methods.
One way of reducing the programming effort is to provide a default class that implements all the interface methods and throws a unique error. We can then implement a Hard-Coded Test Double by subclassing this concrete class and overriding just the one method we expect the SUT to call while we are exercising it. If the SUT calls any other methods, the Pseudo-Object throws an error, thereby failing the test.
Motivating Example
The following test verifies the basic functionality of the component that formats an HTML string containing the current time. Unfortunately, it depends on the real system clock, so it rarely passes!
public void testDisplayCurrentTime_AtMidnight() { // fixture setup TimeDisplay sut = new TimeDisplay(); // exercise SUT String result = sut.getCurrentTimeAsHtmlFragment(); // verify direct output String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals( expectedTimeString, result); }
Refactoring Notes
The most common transition is from using the real component to using a Hard-Coded Test Double.[4] To make this transition, we need to build the Test Double itself and install it from within our Test Method. We may also need to introduce a way to install the Test Double using one of the Dependency Injection patterns if the SUT does not already support this installation. The process for doing so is described in the Replace Dependency with Test Double refactoring.
Example: Test Double Class
Here’s the same test modified to use a Hard-Coded Test Double class to allow control over the time:
public void testDisplayCurrentTime_AtMidnight_HCM() throws Exception { // Fixture setup // Instantiate hard-coded Test Stub TimeProvider testStub = new MidnightTimeProvider(); // Instantiate SUT TimeDisplay sut = new TimeDisplay(); // Inject Test Stub into SUT sut.setTimeProvider(testStub); // Exercise SUT String result = sut.getCurrentTimeAsHtmlFragment(); // Verify direct output String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); }
This test is hard to understand without seeing the definition of the Hard-Coded Test Double. We can readily see how this approach might lead to an Obscure Test caused by a Mystery Guest if the Hard-Coded Test Double is not close at hand.
class MidnightTimeProvider implements TimeProvider { public Calendar getTime() { Calendar myTime = new GregorianCalendar(); myTime.set(Calendar.HOUR_OF_DAY, 0); myTime.set(Calendar.MINUTE, 0); return myTime; } }
Depending on the programming language, this Test Double Class can be defined in a number of different places, including within the body of the Testcase Class (an inner class) and as a separate free-standing class either in the same file as the test or in its own file. Of course, the farther away the Test Double Class resides from the Test Method, the more of a Mystery Guest it becomes.
Example: Self Shunt/Loopback
Here’s a test that uses a Self Shunt to allow control over the time:
public class SelfShuntExample extends TestCase implements TimeProvider { public void testDisplayCurrentTime_AtMidnight() throws Exception { // fixture setup TimeDisplay sut = new TimeDisplay(); // mock setup sut.setTimeProvider(this); // self shunt installation // exercise SUT String result = sut.getCurrentTimeAsHtmlFragment(); // verify direct output String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); } public Calendar getTime() { Calendar myTime = new GregorianCalendar(); myTime.set(Calendar.MINUTE, 0); myTime.set(Calendar.HOUR_OF_DAY, 0); return myTime; } }
Note how both the Test Method that installs the Hard-Coded Test Double and the implementation of the getTime method called by the SUT are members of the same class. We used the Setter Injection pattern (see Dependency Injection) to install the Hard-Coded Test Double. Because this example is written in a statically typed language, we had to add the clause implements TimeProvider to the Testcase Class declaration so that the sut.setTimeProvider(this) statement will compile. In a dynamically typed language, this step is unnecessary.
Example: Subclassed Inner Test Double
Here’s a JUnit test that uses a Subclassed Inner Test Double using Java’s “Anonymous Inner Class” syntax:
public void testDisplayCurrentTime_AtMidnight_AIM() throws Exception { // Fixture setup // Define and instantiate Test Stub TimeProvider testStub = new TimeProvider() { // Anonymous inner stub public Calendar getTime() { Calendar myTime = new GregorianCalendar(); myTime.set(Calendar.MINUTE, 0); myTime.set(Calendar.HOUR_OF_DAY, 0); return myTime; } }; // Instantiate SUT TimeDisplay sut = new TimeDisplay(); // Inject Test Stub into SUT sut.setTimeProvider(testStub); // Exercise SUT String result = sut.getCurrentTimeAsHtmlFragment(); // Verify direct output String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); }
Here we used the name of the real depended-on class (TimeProvider) in the call to new for the definition of the Hard-Coded Test Double. By including a definition of the method getTime within curly braces after the classname, we are actually creating an anonymous Subclassed Test Double inside the Test Method.
Example: Inner Test Double Subclassed from Pseudo-Class
Suppose we have replaced one implementation of a method with another implementation that we need to leave around for backward-compatibility purposes, but we want to write tests to ensure that the old method is no longer called. This is easy to do if we already have the following Pseudo-Object definition:
/** * Base class for hand-coded Test Stubs and Mock Objects */ public class PseudoTimeProvider implements ComplexTimeProvider { public Calendar getTime() throws TimeProviderEx { throw new PseudoClassException(); } public Calendar getTimeDifference(Calendar baseTime, Calendar otherTime) throws TimeProviderEx { throw new PseudoClassException(); } public Calendar getTime( String timeZone ) throws TimeProviderEx { throw new PseudoClassException(); } }
We can now write a test that ensures the old version of the getTime method is not called by subclassing and overriding the newer version of the method (the one we expect to be called by the SUT):
public void testDisplayCurrentTime_AtMidnight_PS() throws Exception { // Fixture setup // Define and instantiate Test Stub TimeProvider testStub = new PseudoTimeProvider() { // Anonymous inner stub public Calendar getTime(String timeZone) { Calendar myTime = new GregorianCalendar(); myTime.set(Calendar.MINUTE, 0); myTime.set(Calendar.HOUR_OF_DAY, 0); return myTime; } }; // Instantiate SUT TimeDisplay sut = new TimeDisplay(); // Inject Test Stub into SUT: sut.setTimeProvider(testStub); // Exercise SUT String result = sut.getCurrentTimeAsHtmlFragment(); // Verify direct output String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); }
If any of the other methods are called, the base class methods are invoked and throw an exception. Therefore, if we run this test and one of the methods we didn’t override is called, we will see the following output as the first line of the JUnit stack trace for this test error:
com..PseudoClassEx: Unexpected call to unsupported method. at com..PseudoTimeProvider.getTime(PseudoTimeProvider.java:22) at com..TimeDisplay.getCurrentTimeAsHtmlFragment(TimeDisplay.java:64) at com..TimeDisplayTestSolution. testDisplayCurrentTime_AtMidnight_PS( TimeDisplayTestSolution.java:247)
In addition to failing the test, this scheme makes it very easy to see exactly which method was called. The bonus is that it works for calls to all unexpected methods with no additional effort.
Further Reading
Many of the “how to” books on test-driven development provide examples of Self Shunt, including [TDD-APG], [TDD-BE], [UTwJ], [PUT], and [JuPG]. The original write-up was by Michael Feathers and is accessible at http://www.objectmentor.com/resources/articles/SelfShunPtrn.pdf
The original “Shunt” pattern is written up at http://c2.com/cgi/wiki? ShuntPattern, along with a list of alternative names including “Loopback.” See the sidebar “What’s in a (Pattern) Name?” on page 576 for a discussion of how to select meaningful and evocative pattern names.
The Pseudo-Object pattern is described in the paper “Pseudo-Classes: Very Simple and Lightweight Mock Object-like Classes for Unit-Testing” available at http://www.devx.com/Java/Article/22599/1954?pf=true.