I previously claimed that you need some integrated tests but as few as possible. There are huge benefits to this approach, but there is a problem. How do you stop your test doubles falling out of line with the real implementations? The answer is to use Contract Tests.
- Create a base test fixture, this is where your tests live. All assertions belong here.
- Subclass this base class with each implementation.
- Override each setup step to provide the implementation that is to be tested.
- All tests should pass for each instance.
In this example there is a SQL repository and an in memory repository. It is not possible to change either in any manner that causes them to behave differently. We can safely use the in memory repository for tests, with confidence that the test double matches the contract of the real implementation.
The test double implementations can be executed on every test run. While real implementations can be relegated to execution prior to commit or during continuous integration. This trade off allows for fast feedback cycles while ensuring all tests are run against production like implementations.