Test Double is a generic term for any case where you replace a production object or procedure with another for testing purposes. In automated unit testing, a test double replaces an object on which the System Under Test (SUT) depends on.
Test doubles are popularly (and incorrectly) called Mock objects, but in reality, a mock object is a very specific type of test double.
The word "mock" is sometimes used in an informal way to refer to the whole family of objects that are used in tests. They are called test doubles.
The key reason for tests doubles is to help us design how our objects communicate. They help in verifying indirect output of the SUT, by checking that it interacted with it’s (direct or indirect) collaborators in an expected way. We do that by replacing the objects the SUT depends on with doubles that record how they are called, such that, we can check if the SUT interacted with it’s dependencies as expected or not, if it did it at all. This is illustrated in the figure below.
Figure 1. Test code stimulating the SUT and checking expectations on test doubles through a Mockery
Mock objects help us move from state-based testing to interaction-based testing, where rather than looking at the objects' state, we look at their interactions and behaviour. This stops us from having to add unneeded getters to our code just to be able to assert they have the right state.
Another reason we use test doubles is for code isolation (Read the "A warning about over-isolating, specially through Mocking" section below before jumping to conclusions). When we’re testing code that depends on another class, we provide the object with a double instance of that class, instead of a real object. That way, we’re making sure that our test will only fail if the SUT is broken, and not if one of it’s dependencies is broken.
Doubles also allow us to replace/override/patch some functions on objects so that we can ease testing. An example of that is when you have written an encoding algorithm which you want to test. This algorithm uses a function getRandomPrime()
from another class (in this example RandomGenerator
). For testing the encoding algorithm you need to know the value of the parameters and the resulting return value for your assertions. To solve the problem, that the return value depends on a random value you can "stub" the class RandomGenerator
and tell the function getRandomPrime()
to return 7 every time it’s called during the test.
| We can also create double objects from interfaces. This makes a lot of sense if we think about it. In many cases, we should actually use doubled interfaces in tests instead of doubled concrete classes. After all, the interface is the contract by which classes agree to talk to the outside world. |
Summarizing, the typical reasons for using test doubles may include:
"These getters we write for testing are cluttering up the design", i.e. adding otherwise unnecessary getters to many objects in order to get object state so that we can verify expectations, ultimately cluttering up the design
Difficulties with integration testing - some parts are slow or expensive to test
Non-deterministic behaviour (date & time, web service APIs, pseudo-random functions, etc.)
Dependency on an external resource: FS, DB, net, printer
improve the performance of our tests
real object hasn’t been written yet
what you’re calling has UI/needs human interaction
simplify test setup
build in smaller increments
Sometimes it is just plain hard to test the system under test (SUT) because it depends on other components that cannot be used in the test environment. This could be because they aren’t available, they will not return the results needed for the test or because executing them would have undesirable side effects. In other cases, our test strategy requires us to have more control or visibility of the internal behaviour of the SUT. When we are writing a test in which we cannot (or chose not to) use a real depended-on component (DOC), we can replace it with a Test Double. The Test Double doesn’t have to behave exactly like the real DOC; it merely has to provide the same API as the real one so that the SUT thinks it is the real one!
— xUnit Test Patterns: Refactoring Test Code - Gerard Meszaros, 2007