It means a test checks what the unit does, as its callers see it, not how it does it inside. Callers see its results and its observable side effects. Then you can refactor freely, and tests fail only when the behaviour really changes.
A test that breaks the rule. It tests UsersService.register and asserts these calls, in this order:
repo.findOneBywith{ email }bcrypt.hashwith 10 roundsrepo.create, thenrepo.save
Now imagine a harmless refactor. You replace the lookup with a unique constraint in the database. Or you replace create plus save with insert. The test breaks, but users still register correctly.
The rewrite. A behaviour test registers a user and checks the results:
- You can find the user by email.
- The stored password is not the plain text, and it verifies against the plain text.
- Registering the same email again fails with a conflict.
The usual warning signs of a bad test are spying on private methods and asserting call order.
- Assert results and observable side effects, not internal steps.
- Tests tied to the implementation break on harmless refactors.
- Example: asserting the order of repository and hashing calls.
- Warning signs are spying on private methods and asserting call order.
Read more