Verdict
Yes
Test the concrete methods of an abstract class once, through a small subclass that exists only in the test, and test each real subclass for the methods it implements.
Why
- Blast radiususers
- Change frequencyrarely
- Detectabilitysame-day
- Reversibilitytrivial
- Test costtrivial
Write one or two tests for the code that an abstract class implements, through a small subclass that exists only in the test. The typical case is an abstract base class in a web application with one shared method that three subclasses inherit. Blast radius is users because the shared method runs in every subclass, and Change frequency is rarely because a base class changes a few times a year. Detectability is same-day, since several features break at once and users report them, and Reversibility is trivial because a revert undoes the damage. Test cost is trivial because a stub subclass takes minutes to write, and rule R12 gives Test minimally.
| When | Decision | Why |
|---|---|---|
| The abstract class declares only abstract methods and has no method bodies | Do not test; the compiler checks that every subclass implements the abstract methods | Detectability drops to immediately, because a subclass that misses a method does not compile |
| A shared method in the abstract class calculates the fees that customers are charged | Test mandatory, including rounding and boundary amounts | Blast radius rises to money and Reversibility to costly, because a wrong charge leads to refunds |
| The abstract class ships in a library that other teams subclass | Test the shared methods, and publish a contract test that each subclass can run | Detectability drops to eventually and Reversibility to with-effort: other teams find the bug after an upgrade |
| New hooks land in the abstract class every week as new subclasses appear | Test the main path and each hook that the abstract class calls | Change frequency rises to constantly |
| A test subclass needs a database or a framework container to start, and the integration tests of the real subclasses assert on the full output of the shared method, header line included | Do not test the abstract class alone; the integration tests of the real subclasses cover its code | Test cost rises to moderate; Detectability stays same-day because those assertions fail when the shared method breaks |
| The abstract class is part of a throwaway prototype that only you run | Do not test; look at the output after each change | Blast radius drops to none |
| A concrete method's result depends on the value that a real subclass's hook returns, such as a total built from per-plan rates | Test: call the concrete method on each real subclass | Detectability moves to eventually: the test subclass runs only its own hook, and a real hook that breaks the method's assumptions returns a plausible result |
What breaks if you don't test
Take ReportExporter, a base class whose export() method writes a header row and then the rows that each subclass returns. A refactor moves the header into a helper and drops the line break after it. Every export now glues the first data row onto the header. Customers see a broken first column in their spreadsheets and report it the same day. No subclass test fails, because each one checks only the rows its subclass returns.
What you lose if you over-test
The common over-test repeats the assertions about the base class in the test of every subclass. With three subclasses, one header change means edits in three test files that check one line. A second over-test checks that each abstract method exists, which the compiler already enforces.
How to test
Test the shared behaviour once, at unit level. Declare a minimal subclass in the test file that returns a fixed value from each abstract method, and assert on the concrete method's result. One main-path test is the minimum; add a regression test for each bug in the base class. Test each real subclass for the methods it implements. When the concrete method's result depends on a real hook's value, also call the concrete method on each real subclass, because the stub runs only its own hook. In Java, Mockito builds the subclass with mock(ReportExporter.class, CALLS_REAL_METHODS).
When the answer changes
- A shared method calculates an amount that customers pay.
- Other teams subclass the base class from your library.
- New hooks land in the base class every week.
Code example
One stub instead of three copies
This test covers export() once:
abstract class ReportExporter {
String export() {
return header() + "\n" + String.join("\n", rows());
}
String header() { return "id,name,created"; }
protected abstract List<String> rows();
}
class ReportExporterTest {
static class Stub extends ReportExporter {
protected List<String> rows() { return List.of("1,Ada,2026-09-01"); }
}
@Test
void writesHeaderLineBeforeRows() {
assertEquals("id,name,created\n1,Ada,2026-09-01", new Stub().export());
}
}
The Stub class has no logic of its own, so the test fails only when export() or header() is wrong. The order, customer and product exporter tests then check only their own rows().
Related questions
FAQ
- Should you unit test abstract classes?
Yes, test the methods that an abstract class implements, with one or two tests through a small test subclass. The abstract method declarations need no test, because the compiler rejects a subclass that misses one, and Python raises a TypeError when you instantiate it.
- Should I unit test my subclasses or my abstract parent class?
Test both, each for its own code: the parent's concrete methods once through a test subclass, and each real subclass for the methods it implements. Repeating the parent's assertions in every subclass test adds an edit per subclass whenever the parent changes and finds no extra bugs. When a parent method's result depends on a subclass's hook, also call that parent method on each real subclass.
- How do I test an abstract class in Java?
Declare a small static subclass in the test class that returns fixed values from the abstract methods, then call the concrete method and assert on its result. Mockito can generate the same subclass with
mock(MyClass.class, CALLS_REAL_METHODS), which runs the real concrete methods.- Should I test abstract methods?
No, an abstract method has no body, so it holds no code that can fail. Test its implementation in each subclass instead.