Mocking libraries are a powerful tool
Mocking tools make it really easy to create stubs, spies, mocks. When you start learning how to test you love them, then after a while you hate them and after that you realize they are great again.
When I started learning about tests, I was developing in Java and JUnit.
In that team, people differentiate between unit and integration tests.
Even if unit tests were just at class/method level, integration tests were related to any kind of infrastructure.
The tool that makes to test units of code independent of other units were the mocking tools, at that time the one I was using was Mockito.
It was really easy to create doubles of your collaborators, inject them in the classes and add predefined behaviors to those test doubles.
This approach was at that time right for me, until we had to refactor things. At that moment, our test doubles were a problem, because if I was changing something in the production code and I didn’t change it in my test doubles inside my tests.
The old tests pass with a behavior of one of their collaborators (the test double) that was not the production behavior.
Also, this happens in the other way around, I can change a mock without changing the real production code and my test passes but in production we will have a bug.
This is why I say that test doubles created with mocking tools hide the contract that exist between the production code and test doubles. They hide it, because if you build your own test doubles, you can always create a contract test to check that both (test doubles and the production implementation) behave the same. Using mocking tools, you cannot do that, because you haven’t created anything that can be run outside your test methods.
There are a lot of reactions to these problems, one of them is to think that test doubles are bad. Then people tend to avoid testing anything with mocking libraries, so they just use e2e tests.
E2E tests are black boxes tests so they don’t have any problem with refactoring, but they introduce a problem much complicated to solve.
The explosion of tests, trying to test complex behaviors with e2e tests is costly, brittle and slow.
E2E tests are higher in the test pyramid because of all of these reasons.
Flakiness in tests
A flaky test is a test that both passes and fails periodically without any code changes.
It’s a natural reaction to think that the solution is e2e tests, I had that reaction too. But then I saw the consequences, long pipelines, flakiness everywhere, people delaying the execution of the whole test suite.
Time to solve a bug detected by an e2e tests is much higher than in an integration or unit test.
Other people think, the problem is using mocking tools. Let’s avoid them so we will not use at all mocks (“the real mocks”), we will just use fakes, stubs written by ourselves. And to be sure that our doubles behave the same as our classes in production, then we can write contract tests between them.
This approach seems to solve the problem completely, but it introduces more time to have something working, as you have to write more code to obtain your test doubles.
Contract testing and test doubles
When we talk about contract testing, people tend to think this is something to do between services that talk through HTTP. Contract testing is much more than that, it’s a way to be sure that every part of your software works correctly with others without paying the cost of creating e2e tests.
At some level of doing TDD, I started being addicted to short feedback loops, so trying to create my own test doubles introduce a delay that I didn’t like.
As faster, you are able to iterate as fast, you are able to improve your design.
I started to think that evolving my code was the easiest and faster way to design the simple possible solution.
Short feedback loops
A feedback loop in learning is a cause-effect sequence where data (often in the form of an ‘event’) is responded to based on recognition of an outcome and that data is used to inform future decisions in similar or analogous situations.
In fact, mocking libraries help you to design APIs faster, to delay decisions of how to implement things and just focusing on what collaborator API you want to have.
One of the key parts of evolutionary design.
In TDD with mocking libraries can create APIs for collaborators really fast, no need to implement them at all, no need to create new files, just the minimum code to play with the SUT (service under test) you are working on.
Then how to solve it, in fact with two rules:
Every time you change a test double created with your mocking library, you need to immediately change that behavior in your production code.
Every time you change code in production, you need to find all the test doubles you are using and update them with the new behavior
If everyone in the team does this, no problem at all.
But we know that we are humans, so we will make mistakes, then let’s try to minimize the number of doubles created with mocking tools we use.
These other two rules can also help:
Just use mocking tools for code you can change.
Just mock interfaces created between layers.
Don’t Mock what you don’t own (basics)
Mocks are easy to use and help us to design easily, but we should not use them to mock things we cannot change.
Mocking tools are good because test doubles are good, but we need to understand their tradeoffs and the power they unveil.