junit-team/junit5 · error · JUnitException

@TestFactory method must not return null

Error message

@TestFactory method must not return null

What it means

Thrown when a @TestFactory method returns null. JUnit Jupiter treats @TestFactory methods as dynamic-test producers; null is neither a single DynamicNode nor a convertible collection/stream, so there is no way to enumerate tests and the framework aborts with an explicit message instead of silently producing zero tests.

Solutions

  1. Return Stream.empty(), Collections.emptyList(), or an empty Iterable instead of null to express 'no dynamic tests'.
  2. Fix the missing return statement or the conditional that fell through to null.
  3. Use a single DynamicTest.dynamicTest(...) when you have exactly one test.

Example fix

// before
@TestFactory
Stream<DynamicTest> factory() {
    if (inputs.isEmpty()) return null;
    return inputs.stream().map(i -> dynamicTest(i, () -> run(i)));
}
// after
@TestFactory
Stream<DynamicTest> factory() {
    if (inputs.isEmpty()) return Stream.empty();
    return inputs.stream().map(i -> dynamicTest(i, () -> run(i)));
}
Defensive patterns

Strategy: validation

Validate before calling

Object result = invokeTestFactory();
if (result == null) {
    throw new IllegalStateException("@TestFactory returned null - return Stream.empty() instead");
}

Prevention

When it happens

Trigger: toDynamicNodeStream(@Nullable Object) is called with a null result from the @TestFactory method; the explicit null check at the top of that method throws JUnitException.

Common situations: Method returns null by accident (early return, missing return statement, conditional return that falls through), or returns null to signal 'no tests' which is not allowed - use Collections.emptyList() or Stream.empty() instead.

Related errors


AI-assisted analysis of junit-team/junit5@f070c699a0 (2026-08-11). Data as JSON: /api/errors/6b1e8238535b8f86. Report an issue: GitHub.

Appendix: 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 f070c699a0)