- Structure your code so it is mostly leaves.
- Unit test the leaves.
- Integration test the rest if needed.
I like this approach in part because making lots of leaves also adds to the "literate"-ness of the code. With lots of opportunities to name your primitives, the code is much closer to being self documenting.
Depending on the project and its requirements, I also think "lazy" testing has value. Any time you are looking at a block of code, suspicious that it's the source of a bug, write a test for it. If you're in an environment where bugs aren't costly, where attribution goes through few layers of code, and bugs are easily visible when they occur, this can save a lot of time.
- https://github.com/testdouble/contributing-tests/wiki/London... (and the rest of the Wiki)
- Most of the screencasts and articles at https://www.destroyallsoftware.com/screencasts (especially this brilliant talk https://www.destroyallsoftware.com/talks/boundaries)
- Integration Tests Are A Scam: https://www.youtube.com/watch?v=VDfX44fZoMc
All of these basically go the opposite way of the article's philosophy:
Not too many integration tests, mostly unit tests. Clearly define a contract between the boundaries of the code, and stub/mock on the contract. You'll be left with mostly pure functions at the leaves, which you'll unit test.
My leaves are either pure functions (FP languages) or value objects that init themselves based on other value objects (OOP languages). These value objects have no methods, no computed properties, etc. Just inert data.
No mocks and no “header” interfaces needed.
On top of that I sprinkle a bunch of UI tests to verify it’s all properly wired up.
Exactly. You expressed my thoughts very succinctly. Though I feel the post tries to say the same just in a lot more words.
Is there any situation where there is integration, but no need to test it?
You seem to be suggesting that if the leaves are thoroughly tested, nothing can go wrong in their integration, but at the same time, I cannot imagine someone believing that.
If you need to create an object, can you pass the name of the class in? Or can the object be created elsewhere and passed in fresh? If you're making a call to a remote service (even your local DB) are you being passed a proxy object?
All of these references can then be provided as a test double or test spy, so long as they are strict about the interface they provide/expect, and you can exhaustively cover whatever internal edge cases you need with unit tests.
Don't _forget_ the integration tests, but my personal opinion is that it usually suffices to have one "success" and one "error" integration test to cover the whole stack, and then rely on unit tests to be more exhaustive about handling the possible error cases.