{"id":"6b1e8238535b8f86","repo":"junit-team/junit5","slug":"testfactory-method-must-not-return-null","errorCode":null,"errorMessage":"@TestFactory method must not return null","messagePattern":"@TestFactory method must not return null","errorType":"exception","errorClass":"JUnitException","httpStatus":null,"severity":"error","filePath":"junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java","lineNumber":139,"sourceCode":"\t\t\t\twhile (iterator.hasNext()) {\n\t\t\t\t\tDynamicNode dynamicNode = iterator.next();\n\t\t\t\t\tOptional<JupiterTestDescriptor> descriptor = createDynamicDescriptor(this, dynamicNode, index,\n\t\t\t\t\t\tdefaultTestSource, getDynamicDescendantFilter(), configuration);\n\t\t\t\t\tdescriptor.ifPresent(dynamicTestExecutor::execute);\n\t\t\t\t\tindex++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch (ClassCastException ex) {\n\t\t\t\tthrow invalidReturnTypeException(ex);\n\t\t\t}\n\t\t\tdynamicTestExecutor.awaitFinished();\n\t\t});\n\t}\n\n\t@SuppressWarnings(\"unchecked\")\n\tprivate Stream<DynamicNode> toDynamicNodeStream(@Nullable Object testFactoryMethodResult) {\n\t\tif (testFactoryMethodResult == null) {\n\t\t\tthrow new JUnitException(\"@TestFactory method must not return null\");\n\t\t}\n\t\tif (testFactoryMethodResult instanceof DynamicNode node) {\n\t\t\treturn Stream.of(node);\n\t\t}\n\t\treturn (Stream<DynamicNode>) CollectionUtils.toStream(testFactoryMethodResult);\n\t}\n\n\tprivate JUnitException invalidReturnTypeException(Throwable cause) {\n\t\tString message = \"Objects produced by @TestFactory method '%s' must be of type %s.\".formatted(\n\t\t\tgetTestMethod().toGenericString(), DynamicNode.class.getName());\n\t\treturn new JUnitException(message, cause);\n\t}\n\n\tstatic Optional<JupiterTestDescriptor> createDynamicDescriptor(JupiterTestDescriptor parent, DynamicNode node,\n\t\t\tint index, TestSource defaultTestSource, DynamicDescendantFilter dynamicDescendantFilter,\n\t\t\tJupiterConfiguration configuration) {\n\n\t\tUniqueId uniqueId;","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/junit-team/junit5/blob/956246301e03d757539f7b76dd5a91f298637563/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java#L121-L157","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\n@TestFactory\nStream<DynamicTest> tests() {\n    return maybeTests; // maybeTests is null\n}\n// after\n@TestFactory\nStream<DynamicTest> tests() {\n    return maybeTests != null ? maybeTests : Stream.empty();\n}","handlingStrategy":"validation","validationCode":"// Guard the @TestFactory return before returning\nStream<DynamicTest> tests = computeTests();\nif (tests == null) tests = Stream.empty();\nreturn tests;","typeGuard":"static boolean isNonNullDynamicSource(Object result) {\n    return result != null && (result instanceof DynamicNode || result instanceof java.util.Collection || result instanceof java.util.stream.Stream || result.getClass().isArray());\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["junit-jupiter","test-factory","dynamic-test","null-check"],"analyzedSha":"956246301e03d757539f7b76dd5a91f298637563","analyzedAt":"2026-08-04T19:25:17.049Z","schemaVersion":2}