junit-team/junit5 · error · JUnitException
@TestFactory method must not return null
Error message
@TestFactory method must not return null
What it means
Thrown by TestFactoryTestDescriptor.toDynamicNodeStream() when a @TestFactory method returns a literal null. A @TestFactory must return a stream/collection/iterator/array of DynamicNode (or a single DynamicNode); null is not a valid dynamic test source and is rejected as a JUnitException.
Source
Thrown at junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java:139
while (iterator.hasNext()) {
DynamicNode dynamicNode = iterator.next();
Optional<JupiterTestDescriptor> descriptor = createDynamicDescriptor(this, dynamicNode, index,
defaultTestSource, getDynamicDescendantFilter(), configuration);
descriptor.ifPresent(dynamicTestExecutor::execute);
index++;
}
}
catch (ClassCastException ex) {
throw invalidReturnTypeException(ex);
}
dynamicTestExecutor.awaitFinished();
});
}
@SuppressWarnings("unchecked")
private Stream<DynamicNode> toDynamicNodeStream(@Nullable Object testFactoryMethodResult) {
if (testFactoryMethodResult == null) {
throw new JUnitException("@TestFactory method must not return null");
}
if (testFactoryMethodResult instanceof DynamicNode node) {
return Stream.of(node);
}
return (Stream<DynamicNode>) CollectionUtils.toStream(testFactoryMethodResult);
}
private JUnitException invalidReturnTypeException(Throwable cause) {
String message = "Objects produced by @TestFactory method '%s' must be of type %s.".formatted(
getTestMethod().toGenericString(), DynamicNode.class.getName());
return new JUnitException(message, cause);
}
static Optional<JupiterTestDescriptor> createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node,
int index, TestSource defaultTestSource, DynamicDescendantFilter dynamicDescendantFilter,
JupiterConfiguration configuration) {
UniqueId uniqueId;View on GitHub (pinned to 956246301e)
Solutions
- Return Stream.empty() (or Collections.emptyList()) instead of null to express 'no dynamic tests'.
- Ensure the returned collection/stream is initialized before the method returns.
- If a single DynamicNode is intended, return it directly (non-null) per the DynamicNode branch in toDynamicNodeStream.
Example fix
// before
@TestFactory
Stream<DynamicTest> tests() {
return maybeTests; // maybeTests is null
}
// after
@TestFactory
Stream<DynamicTest> tests() {
return maybeTests != null ? maybeTests : Stream.empty();
} Defensive patterns
Strategy: validation
Validate before calling
// Guard the @TestFactory return before returning Stream<DynamicTest> tests = computeTests(); if (tests == null) tests = Stream.empty(); return tests;
Type guard
static boolean isNonNullDynamicSource(Object result) {
return result != null && (result instanceof DynamicNode || result instanceof java.util.Collection || result instanceof java.util.stream.Stream || result.getClass().isArray());
} Prevention
- Never return null from a @TestFactory; use Stream.empty() / Collections.emptyList().
- Initialize stream/collection fields used by @TestFactory at declaration.
- Treat 'no tests' as Stream.empty(), not null.
When it happens
Trigger: A @TestFactory method whose body returns null (e.g. a field was never initialized, a computed stream evaluated to null, or an early-return null was used as a 'no tests' sentinel).
Common situations: Conditionally returning null from a @TestFactory to mean 'skip'; refactoring a method that previously returned a collection so it now returns an uninitialized field; a mock-based factory returning null by default.
Related errors
- Assumption failed:
- Assumption failed
- This class must not be instantiated
- Implement generateDisplayNameForNestedClass(List<Class<?>>,
- Implement generateDisplayNameForMethod(List<Class<?>>, Class
AI-assisted analysis of junit-team/junit5@956246301e (2026-08-04).
Data as JSON: /data/errors/6b1e8238535b8f86.json.
Report an issue: GitHub.