Dependency Inversion in Practice
Constructor injection in plain Java, and why it makes tests need no mock library.
Open this lesson in the learning hubKey points
- High level policy should not depend on low level detail. Both should depend on a small interface.
- The interface belongs to the policy, not to the detail.
SignupServicedeclares the Clock it needs. - Take collaborators through the constructor and store them
final. The object is then always usable. - Never call
newon a collaborator inside the class: that is the line you cannot swap in a test. - A fake is often five lines and clearer than a mock: implement the interface and record what it was told.
- This is exactly what constructor injection in Spring does. The pattern works fine with no framework at all.
Example
import java.util.ArrayList;
import java.util.List;
public class Main {
// The policy declares the small interfaces it needs. Details implement them.
interface Clock { String now(); }
interface Audit { void record(String line); }
static class SignupService {
private final Clock clock;
private final Audit audit;
SignupService(Clock clock, Audit audit) { // injected, never constructed inside
this.clock = clock;
this.audit = audit;
}
void signup(String user) { audit.record(clock.now() + " signup " + user); }
}
static class RecordingAudit implements Audit { // a test double, no mock library
final List<String> lines = new ArrayList<>();
@Override public void record(String line) { lines.add(line); }
}
public static void main(String[] args) {
// Production wiring: this is exactly what a framework does for you.
new SignupService(() -> "2026-01-01T09:00", System.out::println).signup("ada");
// Test wiring: same class, fixed clock, captured output.
RecordingAudit spy = new RecordingAudit();
new SignupService(() -> "FIXED-TIME", spy).signup("grace");
System.out.println("captured -> " + spy.lines);
}
}
Depend on a small interface you own, and take it through the constructor.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.