One common pitfall when operating in a mock-heavy TDD context is the tautological test. This is especially true for code which involves awkward dependencies.
“I want to avoid talking to the real [database|API endpoint|etc.] here in this class,” you say. A good instinct!
“The tests will be slower and less reliable if they need this kind of hard external dependency.” I can’t argue with you there.
“To make everyone’s life easier, I am going to define an interface for the dependency and inject it, thus solving these testing woes.” Hold on — this is where you should pause and take a look at what this class is actually intended to do.
If the class in question is, say, “DatabaseBackend” then it seems likely that you are heading straight toward the testable adapter anti-pattern. A class like DatabaseBackend would (hopefully) only exist to wrap all the database backend logic — and probably itself implements some sort of interface so that other parts of your system don’t have to deal with database details.
Sorry to be the bearer of bad news, but there is no sensible way to avoid running that database code if you really want to test that it works. Without the real dependency, you are (at best) proving that you can write a mock implementation that matches what your test will assert.
Keep in mind that this sentiment applies specifically to adapters — code that exists to translate “what’s outside” (externals) to “what’s inside” (your core logic/model). With judicious application of the ports/adapters/simulators architecture, you would tend to push the vast majority of awkward externals to the edge of your system. What you are left with is ideally a small surface area of truly externally facing code. Since this code is far from the core of the system, the corresponding tests should be relatively concise and constrained. Nevertheless, they are tests of the real dependency.
The consolation prize is that this kind of adapter code tends to change far less often than the core business logic. Once it reaches an acceptable level of maturity/stability, you can probably get away with pushing such code into a separate module with separate tests that will not interrupt the flow of the day-to-day TDD cycle.
Just remember: when it comes to validating adapter code, nothing beats the real thing.