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.
Mocks are easy to use and help us to design easily, but we should not use them to mock things we cannot change.
Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don’t expect and are checked during verification to ensure they got all the calls they were expecting.
https://martinfowler.com/bliki/TestDouble.html
It is really easy to create a mock in a test to accelerate our feedback loop.
For example, when I’m writing a controller I can easily mock a use case to avoid thinking on the business algorithm I have to implement, so I can concentrate in the problems the controller has to solve.
In this way I can with the mock define how need to behave the real implementation of the use case without writing it.
The test passes, my controller work and if I create an empty collaborator I also can deploy to prod as this new controller is not used yet by anyone.
Mocks are great for this type of things, but mocks also introduce a problem.
As I said before, every mock you write defines a contract between two parts, the mock itself and the real implementation.
But there is no way to validate that contract, apart from the signature.
So if one part of the contract change you have to manually change the other part, if not you introduce a bug in your system.
If my real use case instead of one exception is throwing another one not expected by my tests but my mocks in those tests throw the old exception, test passes, and we have a bug in production.
This is the trade-off, reducing feedback loop vs harder to maintain. Fakes for example require more work writing them, but we can create contract tests to check that real implementations behave as them in the things we are interested.
The disaster
Let’s imagine for one moment what means to use a mock in a code we cannot change.
For example a third party library, let’s say axios, we are using version 0.27 for axios, and we mock it in our tests. After some few days, we have our code in prod helping our customers and everything is working fine (short feedback loop).
But one year later, the axios community decide to do a breaking change in axios so v1.0.0 requires some changes in your code.
Because of a security incident, you need to upgrade your version to the latest one, because it is the only one that solves the incident.
You don’t remember that you mocked axios, so you update the version and all of your tests passes, you are happy, then you deploy to prod.
The disaster came, everything is broken, errors everywhere. (harder to maintain)
What happened here?, yes that you mocked something you didn’t own, so you didn’t control what changed and what not changed in that third party library.
What to do instead?, use the real library in your tests and fake the server you are calling. Firstly because you will check that your code works as you expect with axios in this case, and second because that allows you to write a contract test between your faked server and the real one.
Just if you don’t imagine how to fake a server, you can use wiremock, or docker to run some servers you want to talk, like a database or redis or elasticsearch. Anything that helps you to simulate the real infrastructure you will find in prod, even you can use a testing infrastructure ephemeral for your tests.
E2E tests
Some people when realize this about mocks tend to think to avoid them completely and create just e2e tests to verify that the whole logic of the system is as they expect.
Please try to avoid that, if you have too many e2e tests you will face several problems really hard to manage:
E2E tests are slow
E2E tests are flaky
Explosion of tests
In general, if you base your test strategy on e2e tests you will see people replaying pipelines, devs not caring about them, QAs having a list of important and not so important tests. All of them crazy scenarios.
I give you a better approach in my opinion, just mock what you own and just mock interfaces.
Minimize the number of interfaces you create between layouts, so a controller need to call an interface to entry in the business logic.
A use case needs to call an interface you own to access a repository, etc.
But if a use case needs to call or use other classes in the business logic, don’t mock them, create a social unit test for your use case.
Unit tests are good for business logic, integration tests are good for infrastructure code.
So if you see that you are changing the implementation of an interface, think that there is probably a mock associated to that interface, go there and change also that mock.
Design your code in a way that mocks help you to have short feedback loops, but use mocks in places easy to identify, so we can easily maintain them.