We use tests to verify that a function behaves as expected. When a function has side effects, it means that it depends on something external. This happens when we test a unit of code and we need another unit of code as a dependency. The primary unit of code is called the system under test (SUT) and the secondary unit of code is called the collaborator.
This is when the Test Double comes in. The name Test double comes from 🦸♂️ Stunt double in movies. For testing purpose, it is used to replace a real implementation of a collaborator. We use doubles to:
- speed up test execution. Network requests can be slow.
- control and manipulate production services that should not work during test such as a mailing service.
- have reliable tests. If we have predictable inputs/outputs, then we do not depend on external conditions/events such as networking.
An advantage of test doubles is that they focus on the interesting part of the code we want to verify. Our tests become easier to write/read/understand.
Type of test doubles
A dummy object is a substitute of a real implementation where we don’t care about the data or how it is used. It doesn’t interact or affect the behavior we are testing. They are used to fill parameter lists. A dummy is never used.
A fake object is similar to a production implementation, but takes some shortcut that make them not suitable for production (an in memory database for example).
A stub is a substitute of a real implementation. It reduces the complexities of creating a real object. We use them to trigger specific code path which leads to predefined behavior. A stub holds predefined data with its methods answering calls during test, regardless of the input. Its purpose is to inject predefined inputs to the SUT.
A spy is like a stub that can also verify that something happened. It stores states of an object / information about function calls (number of times a function is called, arguments called, returned values, error thrown…). Its purpose is to observe actions that happen inside a collaborator.
A mock object is a substitution of a real implementation. It is a production implementation with a specific configuration so we get the expected result. Its implementation defines expectations about the succession of method invocation.