The two styles differ in what a "unit" is and in what the test checks. For NestJS services I lean towards the classical style.
Classical (Detroit) style: a unit is a behaviour. One behaviour may use several real objects.
- You use real collaborators (the other objects your code works with) when they are fast and deterministic. Deterministic means they give the same result every time.
- You replace only the awkward ones, such as the database, the clock or an external API.
- Tests check state: the returned value, or what ended up in a fake repository.
Mockist (London) style: a unit is one class, and every collaborator is a mock.
- Tests check interactions: which methods were called, with which arguments.
- It supports outside-in design, where you start from the outer layer and design inwards.
- But it ties tests to how the class talks to its collaborators. So refactoring breaks the tests, even when the behaviour stays the same.
For Nest services I use:
- real domain helpers;
- test doubles only at process boundaries, such as repositories, HTTP clients and queues;
- assertions on outcomes.
I check an interaction only when the interaction is the outcome, for example "an email was sent".
- In the classical style, a unit is a behaviour, collaborators are real, and tests check state.
- In the mockist style, a unit is one class, all collaborators are mocked, and tests check interactions.
- Mockist tests break more often when you refactor.
- Check interactions only when the interaction is the visible outcome.
Read more