JUnit4TestAdapter is a compatibility bridge that exposes a JUnit 4 test class through the older JUnit 3 junit.framework.Test API. It creates a JUnit 4 Runner for the supplied class, then translates JUnit 3 operations such as run(TestResult), countTestCases(), and getTests() into JUnit 4 runner and description operations. It would typically be used when a JUnit 3 test suite, test runner, or build tool needs to execute JUnit 4 tests without being rewritten. The adapter also supports JUnit 4 filtering, sorting, ordering, descriptions, and ignored-test handling.
public class JUnit4TestAdapter implements Test, Filterable, Orderable, Describable
The class implements:
Test: JUnit 3-compatible execution and test-count behavior.Filterable: Allows tests to be filtered.Orderable: Allows tests to be explicitly ordered.Describable: Exposes a JUnit 4 Description tree.public JUnit4TestAdapter(Class<?> newTestClass)
public JUnit4TestAdapter(Class<?> newTestClass, JUnit4TestAdapterCache cache)
Contract:
newTestClass is the JUnit 4 test class to adapt.JUnit4TestAdapterCache.null; the implementation does not explicitly validate them.countTestCases()run(TestResult result)TestResult.getTests()List<Test>.getTestClass()getDescription()removeIgnored(Description description)toString()filter(Filter filter)NoTestsRemainException if filtering removes every test.sort(Sorter sorter)order(Orderer orderer)InvalidOrderingException.NoTestsRemainException.InvalidOrderingException.NullPointerExceptions.No thread-safety guarantee is visible:
filter, sort, and order may mutate runner state.This class is specifically designed for compatibility between:
Test, TestResultRunner, Description, Filter, Sorter, and OrdererA client can use it from a JUnit 3-style suite:
Test test = new JUnit4TestAdapter(MyJUnit4Test.class);
TestResult result = new TestResult();
test.run(result);
The main components are:
JUnit4TestAdapterRunnerRequestclassWithoutSuiteMethod deliberately requests class-based execution without using a JUnit 3-style suite method.JUnit4TestAdapterCacheTestResult notifier used during execution.DescriptionIgnoreThe design is primarily an Adapter pattern:
JUnit 3 consumer
|
v
JUnit4TestAdapter
|
v
JUnit 4 Runner
|
v
JUnit 4 test class
The adapter also uses a cache/translation layer to avoid repeatedly converting JUnit 4 descriptions into JUnit 3 Test objects.
this.fRunner = Request.classWithoutSuiteMethod(newTestClass).getRunner();
countTestCases() delegates to fRunner.testCount().getDescription().run(TestResult).TestResult events.public void run(TestResult result) { this.fRunner.run(this.fCache.getNotifier(result, this)); }
getTests() obtains the adapter’s JUnit 4 description.Test objects.public List<Test> getTests() { return this.fCache.asTestList(this.getDescription()); }
removeIgnored recursively walks the tree.filter delegates to Filter.apply(fRunner).sort delegates to Sorter.apply(fRunner).order delegates to Orderer.apply(fRunner).These operations modify or configure the underlying runner, so they should normally happen before execution.
JUnit4TestAdapterResponsibility: Provide a JUnit 3-compatible view of a JUnit 4 test class.
It owns:
public JUnit4TestAdapter(Class<?> newTestClass, JUnit4TestAdapterCache cache) {
this.fCache = cache;
this.fNewTestClass = newTestClass;
this.fRunner = Request.classWithoutSuiteMethod(newTestClass).getRunner();
}
Important logic:
suite() method because it uses classWithoutSuiteMethod.countTestCases()public int countTestCases() { return this.fRunner.testCount(); }
Returns the runner’s test count. It does not manually traverse the description tree.
run(TestResult result)Delegates execution to the JUnit 4 runner while supplying a notifier adapted to JUnit 3.
getTests()Delegates description-to-test conversion to JUnit4TestAdapterCache.
getTestClass()Returns the exact Class<?> supplied to the constructor.
getDescription()public Description getDescription() {
Description description = this.fRunner.getDescription();
return this.removeIgnored(description);
}
Returns a cleaned description tree rather than the runner’s raw tree.
removeIgnored(Description description)public Description removeIgnored(Description description) {
if (this.isIgnored(description)) { return Description.EMPTY; }
Description result = description.childlessCopy();
for (Description each : description.getChildren()) {
Description child = this.removeIgnored(each);
if (child.isEmpty()) continue;
result.addChild(child);
}
return result;
}
Responsibilities:
childlessCopy().Description.EMPTY for ignored nodes.isIgnored(Description description)Checks whether the description has an @Ignore annotation.
toString()Returns the fully qualified class name, which is useful in logging and JUnit 3 reporting.
filter, sort, and orderThese are thin delegation methods that apply JUnit 4 manipulation objects to the stored runner.
The class does not execute test methods itself. It delegates execution entirely to JUnit 4. Its main responsibility is translating:
removeIgnored is a depth-first recursive traversal:
For a description tree with n nodes:
O(n)O(h) recursion stack plus the newly constructed description tree, where h is tree depth.A notable behavior is that ignored tests are explicitly removed from getDescription(), but countTestCases() delegates directly to Runner.testCount():
public int countTestCases() { return this.fRunner.testCount(); }
Depending on the JUnit runner implementation, the reported count and the cleaned description may not always represent exactly the same set of tests.
Filtering, sorting, and ordering operate on the same runner instance used for execution. Reusing the adapter after applying different transformations may produce state-dependent behavior.
If an ignored node is removed, its parent may become empty. Empty children are skipped, and the root can potentially become an empty description.
removeIgnored exposes what appears to be an internal tree transformation operation.The most branch-heavy logic is the recursive ignored-test removal:
for (Description each : description.getChildren()) {
Description child = this.removeIgnored(each);
if (child.isEmpty()) continue;
result.addChild(child);
}
This path is difficult to reason about because:
Description.EMPTY.In plain English:
The other stateful path is the interaction between filtering, sorting, ordering, and execution:
run.removeIgnored private unless external callers genuinely need it.Objects.requireNonNull(newTestClass, "newTestClass");
Objects.requireNonNull(cache, "cache");
countTestCases() includes ignored tests.this. qualifiers where they do not add clarity.childDescription instead of each and child.getDescription() and getTests().getTests() if callers should not mutate the result.getDescription().filter, sort, or order after execution.