Avoid mocking libraries
Using Test Doubles is a key skill to master when it comes to automated testing - but relying too much on a mocking library can have unexpected side effects.
Hello, developers! 🚀
Welcome back to the Learn Agile Practices newsletter, your weekly dose of insights to power up your software development journey through Agile Technical Practices and Methodologies!
Before starting, let me remind you that a new edition of my free 1-hour webinar about TDD is coming in 3 days, on Friday 24th at 5 pm CEST! I will introduce the practice, and ensure you avoid the usual mistakes and misconceptions that most people fall into, in a free 1-hour session with Q&A included!
Book your free session 👉 here (or feel free to pay what you want!)
Now, let's dive into today's micro-topic!
Not mocks, but Test Doubles
In the Software Development world, and specifically in the testing context, we usually use the word Mocks to refer to Test Doubles, which is not entirely correct: Mocks are just one type of test double.
Test doubles borrow the name from the stunt doubles of movies: in the same way that a stunt double is used in place of a real actor for safety reasons, a test double is any kind of object used in place of a real object for testing purposes, usually when that object is a dependency of another one we are testing.
There are 4 types of test doubles: mocks, stubs, fakes, and spies -
mocks: a double that simulates the behavior of dependencies and verifies the interaction between units of code
stubs: a double that responds with some pre-programmed output when that method is called in the code
fakes: a handmade stub
spies: a handmade mock
To understand how to use them we need to briefly discuss interfaces and CQS first.
Interfaces, Commands, and Queries
First of all, in order to use doubles correctly, you need to use interfaces properly: you should always program to an interface, not an implementation - which means that an object should always depend on an interface, not an implementation.
By using interfaces as dependencies, we ensure our class is only depending on the promises that the interface makes, and not the implementation details that the concrete class uses to respect that behavior - which makes it easier to replace that dependency with a double.
But it’s not enough!
If you want to make use of doubles effectively in your tests, there is another best practice you should consider: the Command-Query Separation (CQS) principle.
The CQS principle states that every class can only expose two types of methods:
Commands: methods that mutate the state but do not answer any portion of that state.
Queries: methods that answer with the current state (all or a portion of it) but do not modify that state before answering.
When the CQS pattern is in place, we can take advantage of it to separate the two types of responsibility and build a more readable and maintainable code - but that’s not all, because it also makes it easier to identify a usage pattern for test doubles - let’s update the previous list:
mocks: a double that simulates the behavior of dependencies and verifies the interaction between units of code → can effectively replace a command method
stubs: a double that responds with some pre-programmed output when that method is called in the code → can effectively replace a query method
spies: a handmade mock to replace a command method
fakes: a handmade stub to replace a query method
Be careful with doubles
Test Doubles are a powerful testing technique: they allow you to isolate a unit of behavior to build a unit test around it, enabling the ability to build a test suite where unit tests are the biggest type of tests, following the best practice of the Pyramid of Test.
While test doubles are a very powerful tool, we need to be careful about the usage of mocking libraries to configure our doubles: the biggest drawback when take advantage of a mocking library for mocks and stubs is that we are including implementation details in our tests.
Let’s have a look at the following example:
In the example above, the test is tightly coupled with the implementation: if the class Person
change the way it interacts with its dependency, the test will have to change too.
Let’s now have a look at the same test with a manual spy:
This time, if the implementation changes, the test will not change - the FakeDatabase
class may change if needed, but not the test itself.
With more complex examples and systems, it becomes evident pretty soon the impact of heavy usage of mocking libraries - and at the same way, benefits are immediately visible once you move to spies and fakes.
As always, balance is key and there are some tips to follow that can help you achieve success in using doubles:
do not mock a collaborator that is only used by the class you are testing
do not implement real behavior on doubles
only mock direct collaborators/dependencies
only use doubles to replace classes that you own
verify as little as possible in every test
🧠 Test Yourself in 1 minute:
💡 Did you know? An interactive activity, like quizzes or flashcards, can boost your learning!
Take a 1-minute quiz and test how clear it was what you just read! 🤔 Don't miss out on the opportunity to boost your learning—try now!
A Test Double is...?
Insights Recap
A Test Double is any kind of object used in place of a real object for testing purposes, usually when that object is a dependency of another one we are testing.
There are 4 types of test doubles:
mocks: a double that simulates the behavior of dependencies and verifies the interaction between units of code.
stubs: a double that responds with some pre-programmed output when that method is called in the code.
spies: a handmade mock
fakes: a handmade stub
In order to use doubles correctly, you need to use interfaces properly: you should always program to an interface, not an implementation
You can also take advantage of the Command-Query Separation (CQS) principle, that every class can only expose two types of methods:
Commands: methods that mutate the state but do not answer any portion of that state.
Queries: methods that answer with the current state (all or a portion of it) but do not modify that state before answering.
We can use CQS to identify a pattern of usage for test doubles:
mocks: a double that simulates the behavior of dependencies and verifies the interaction between units of code → can effectively replace a command method
stubs: a double that responds with some pre-programmed output when that method is called in the code → can effectively replace a query method
spies: a handmade mock to replace a command method
fakes: a handmade stub to replace a query method
Test Doubles are a powerful testing technique that allows you to isolate a unit of behavior to build a unit test around it, but we need to be careful about the usage of mocking libraries:
when using a mocking library the test is tightly coupled with the implementation
we can avoid this by implementing manual fakes and spies
Some tips for using Test Doubles effectively:
do not mock a collaborator that is only used by the class you are testing
do not implement real behavior on doubles
only mock direct collaborators/dependencies
only use doubles to replace classes that you own
verify as little as possible in every test
Until next time, happy coding! 🤓👩💻👨💻
Go Deeper 🔎
Legenda
📚 Books
📩 Newsletter issues
📄 Blog posts
🎙️ Podcast episodes
🖥️ Videos
👤 Relevant people to follow
🌐 Any other content
🖥️ How can I trust my test suite? (slides here)
🖥️ Using a Test Double to Find Bugs (Guided Learning Hour by Emily Bache)