Once you learn JUnit, the next step to learn test-driven development is to begin learning about test doubles.
Remember, unit tests – which are foundational to test-driven development – require us to test a class in isolation. Achieving that isolation isn’t difficult, but there are some definite prerequisites.
This article builds on the last, in particular by using vocabulary defined there. If you did not read it and you’re finding that vocabulary used in this article is unfamiliar, go read that article and then come back here.
For this article, let’s assume that you are using some form of dependency injection to get collaborators into the test subject. We’ll also assume that dependencies are used through an interface.
Many people will use testing as a reason for creating interfaces. While should be a good motivation for most, it’s not actually the highest or most compelling reason.
A better reason to create interfaces is so that you can adhere to the Dependency Inversion Principle and thereby keep policy and mechanism separate. I say this is a higher and more compelling reason because separating policy and mechanism correlates directly to the long-term preservation of business value.
If you’re adding tests to a legacy codebase you may have collaborators that are represented by a concrete type though. In this case, your only real option is to extend the collaborator class to override behavior.
What is a Test Double?
So that we’re all on the same page, and because some of you may be undereducated about test doubles, let’s have a look at how I use the word.
A test double is a class whose sole purpose in life is to help ensure that the system being built works as expected. Depending on the type of test double, it may have no behavior or quite a bit of behavior. However, in all cases, if you looked at the class, we would agree that the test double has no place in production use.
Every test double should have a clear reason for existing though – it should facilitate some necessary part of testing one or more test subjects.
Since this is sounding awful hand-wavy (jazz hands, if you prefer), and I want it to be very concrete and understandable, let’s dig into the various types of test doubles. I think it’ll help a lot.
Types of Test Doubles
There are many different types of test doubles. Each achieves some testing goal. All can be simulated with a mocking framework.
The problem with mocking frameworks is that, if you need to test the same behavior of the same test subject in different ways, there can be a lot of setup needed to cause it to behave as expected. In these cases, writing a custom test double can bring simplicity and reduce duplication.
Custom test doubles shine particularly bright when you have a common interface with lots of different implementations that share a common set of collaborators. In this case, the same test doubles can be used across all tests – really giving you bang for your buck.
Test Double Type 1: The Dummy
A dummy test double matches the type of a parameter, but provides no useful behavior. Its sole purpose is just filling that slot in the parameter list.
When to Use a Dummy Test Double
A dummy can be used anytime you have an argument that you believe is not used.
How to Implement a Dummy Test Double
The simplest implementation of a dummy is the null value. After all, if the dummy is truly unused, no NullPointerException should be triggered.
You could also use an anonymous or named implementation of an interface which is defined to do nothing.
Additional Thoughts on Dummy Test Doubles
As mentioned above, if your expectation is that the dummy is never used, you may be better off using a different test double type. My personal recommendation is that you use a spy (see below) because doing so will allow you to verify that the test double is truly not called.
There’s a deeper thought here though. If you have a parameter that is unused, you should be asking why it is there to begin with. While there may be some legitimate cases for this, there aren’t many, and there’s a good chance that you would be well served to look at the design and refactor to avoid use of the parameter.
Test Double Type 2: The Fake
A fake is a bit more complex than a dummy. It has some form of working implementation, but isn’t something you’d ever use in production.
When to Use a Fake Test Double
Fakes can be useful when the real implementation is either not available or would significantly slow-down the tests. The canonical use case for a fake is the hand-written, in-memory database.
How to Implement a Fake Test Double
The implementation of a fake will vary depending depending on what the real collaborator would do and what testing needs you are trying to satisfy.
In the case of a database, your goals are to satisfy the needs of a service layer – inserting data and then being able to retrieve it. Or, perhaps, throwing some sort of exception if what you’re looking for isn’t there.
Additional Thoughts on Fake Test Doubles
The more expensive calling a real implementation is, the less you will want to use it and the more you’ll want to replace it with something else.
Fake test doubles are really useful for replacing all sorts of external dependencies. For example, consider a situation where you’re calling an external service over HTTP. While this might be great in production, having that dependency in your unit tests will not only slow them down, but will cause your entire build to fail if the team that maintains that service takes it down for maintenance.
Test Double Type 3: The Stub
The job of a stub is to return canned answers that enable you to test specific outcomes within a test subject.
When to Use a Stub Test Double
If your test subject has any logical branching, stubs are essential. The canned answers they provide allow you to ensure that those logical branches are reachable and testable.
How to Implement a Stub Test Double
It is common for stubs to be written with a set of canned answers. However, if the stub has potentially broad applicability, it may be better if it is built in a way that allows it to be programmed on a per-test-case basis.
Additional Thoughts on Stub Test Doubles
The more complex the requirements you are wanting to stub, the more you may want to consider a mocking framework. We’ll talk about those in an upcoming article.
Test Double Type 4: The Spy
A spy test double tracks how it is called. This could include metrics as simple as the number of calls or as complex as preserving parameters used during invocation.
When to Use a Spy Test Double
Spies should be used when you need more information about how a collaborator is called.
How to Implement a Spy Test Double
Simple spy implementations may be a variation on the dummy (described above). More complex ones will track all parameters and invocation counts.
Additional Thoughts on Spy Test Doubles
A trick I like to use is to create the spy following the Decorator Pattern. This allows me to use the spy in combination with other test double types, such as a fake (described above).
Test Double Type 5: The Mock
Mocks are typically more complex than other types of test doubles, and may well combine elements of multiple other test double types.
When to Use a Mock Test Double
Mocks are useful when you need complex stubbing behavior, or when you want to combine elements of both stubbing and spying.
How to Implement a Spy Test Double
Mocks may be hand-written or created by using a mocking framework.
Additional Thoughts on Mock Test Doubles
While most people tend to use a mocking framework to create mocks, hand-writing them can be a useful approach too. For example, if the mock is commonly used in the same way, hand-writing the mock may reduce redundancy and improve readability.
As mentioned above, I find I can often get great utility from a combination of a spy – implemented as a decorator – and a stub.
Wrapping It Up
When you use a particular test double – and whether or not you choose a framework to help – can be highly situational. If, for example, you find that you have little repetition among tests, a mocking framework might provide a lighter approach.
If, on the other hand, you are seeing a lot of repetitive use of a mocking framework being applied the exact same way, you should consider creating hand-written test doubles of the appropriate type and using them instead. Doing so can greatly simplify your tests making it easier to understand and maintain them.
That’s it for this now! Until next time, this is Eddie Bush with Craftsmanship Counts reminding you to keep your tests passing and your code clean!