Using test doubles to replace dependencies in tests has become a standard practice.
Generally, there are three main types of components that should be substituted with test doubles during testing:
- Components that interact with resources outside the memory of the current process, such as databases, caches, files, HTTP requests, or queues.
- Components that rely on randomness or the current date and time.
- Components that, while not falling into the above categories, require a significant amount of time to execute. For unit tests, anything that takes longer than 0.1 seconds is typically considered too slow.
The main reasons for substituting these components with test doubles are:
- Speed: Components in the first category often take a long time to complete their tasks, and the third category is specifically concerned with this issue.
- Consistency: Tests need to yield the same results every time they run. This is challenging when input data varies between runs, which is common with the second category and, at times, the first.
- Isolation: Tests should be able to run at any time (second category) and in any environment (first category). They must not rely on external setups that are beyond the developer’s control or may be unavailable (first category).
Substituting a component that does not fall into any of the categories mentioned above with a test double is considered a code smell. This often indicates that a component has excessive dependencies, including hidden ones, making it hard to instantiate. The root cause is typically poor component design, or a lack of design altogether.