DIP and testability through controlled dependencies
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
DIP often improves testability by giving business logic a controlled seam. If OrderService depends on an application PaymentGateway, a test can pass a small fake that returns approval, decline, or a timeout-like result without calling a real provider. This makes edge cases deterministic and keeps a unit test focused on the service rule.
I do not equate testability with Mockito. A fake or real in-memory collaborator may be clearer, and many concrete classes can also be tested or mocked. I verify interactions only when the interaction is the contract—for example, confirming that a gateway is charged once after validation. If I can prove behavior through the returned result or state, that is usually less coupled to implementation. Integration tests still remain necessary for the real adapter and configuration.
DIP and testability through controlled dependencies
Direct Answer
DIP can create a test seam by letting a unit receive a controlled collaborator instead of requiring a particular external implementation. Tests may use a fake, stub, mock, or real in-memory component. Mocks and interaction verification are optional: verify an interaction only when that interaction is part of the contract; otherwise prefer observable results and state.
Code Example
interface PaymentGateway {
boolean charge(int cents);
}
final class OrderService {
private final PaymentGateway gateway;
OrderService(PaymentGateway gateway) { this.gateway = gateway; }
String checkout(int cents) {
return gateway.charge(cents) ? "confirmed" : "declined";
}
}
final class FakeGateway implements PaymentGateway {
boolean approved;
int charged;
FakeGateway(boolean approved) { this.approved = approved; }
public boolean charge(int cents) { charged++; return approved; }
}
FakeGateway fake = new FakeGateway(false);
OrderService service = new OrderService(fake);
assertEquals("declined", service.checkout(500));
The result assertion proves the policy branch. Checking fake.charged is useful only if call count is part of the required behavior.
Common Mistakes & Pitfalls
- Saying only interfaces are mockable: many tools support concrete classes, and fakes often need no mocking framework.
- Claiming internal
newalways forces real infrastructure: it depends on what is constructed and the available seams. - Verifying every call: this couples tests to implementation order.
- Treating a Spring context test as automatically wrong: it is an integration test and can be valuable for wiring.
- Saying TDD requires DIP: TDD works with many designs; useful seams simply make some units easier to isolate.
Check your understanding
Answer these without looking back at the article.
- What is Mockito and how does it work?
- What is the difference between a mock and a stub?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.