{"record":{"id":"6fb798c9debb17c2","repo":"junit-team/junit5","slug":"selector-selector-did-not-yield-unique-test-des","errorCode":null,"errorMessage":"Selector ${selector} did not yield unique test descriptor: ${stringRepresentation}","messagePattern":"Selector (.+?) did not yield unique test descriptor: (.+?)","errorType":"exception","errorClass":"JUnitException","httpStatus":null,"severity":"error","filePath":"junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java","lineNumber":236,"sourceCode":"\t\t\t\tFunction<TestDescriptor, Optional<T>> creator) {\n\t\t\tif (parent != null) {\n\t\t\t\treturn createAndAdd(parent, creator);\n\t\t\t}\n\t\t\treturn resolve(parentSelectorSupplier.get()).flatMap(parent -> createAndAdd(parent, creator));\n\t\t}\n\n\t\t@Override\n\t\tpublic Optional<TestDescriptor> resolve(DiscoverySelector selector) {\n\t\t\t// @formatter:off\n\t\t\treturn EngineDiscoveryRequestResolution.this.resolve(selector)\n\t\t\t\t\t.map(Resolution::getMatches)\n\t\t\t\t\t.flatMap(matches -> {\n\t\t\t\t\t\tif (matches.size() > 1) {\n\t\t\t\t\t\t\tString stringRepresentation = matches.stream()\n\t\t\t\t\t\t\t\t\t.map(Match::getTestDescriptor)\n\t\t\t\t\t\t\t\t\t.map(Objects::toString)\n\t\t\t\t\t\t\t\t\t.collect(joining(\", \"));\n\t\t\t\t\t\t\tthrow new JUnitException(\n\t\t\t\t\t\t\t\t\"Selector \" + selector + \" did not yield unique test descriptor: \" + stringRepresentation);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (matches.size() == 1) {\n\t\t\t\t\t\t\treturn Optional.of(getOnlyElement(matches).getTestDescriptor());\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn Optional.empty();\n\t\t\t\t\t});\n\t\t\t// @formatter:on\n\t\t}\n\n\t\t@SuppressWarnings(\"unchecked\")\n\t\tprivate <T extends TestDescriptor> Optional<T> createAndAdd(TestDescriptor parent,\n\t\t\t\tFunction<TestDescriptor, Optional<T>> creator) {\n\t\t\tOptional<T> child = creator.apply(parent);\n\t\t\tif (child.isPresent()) {\n\t\t\t\tUniqueId uniqueId = child.get().getUniqueId();\n\t\t\t\tif (resolvedUniqueIds.containsKey(uniqueId)) {\n\t\t\t\t\treturn Optional.of((T) resolvedUniqueIds.get(uniqueId).getTestDescriptor());","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/junit-team/junit5/blob/f070c699a08b5d8393df9afd147af5c5e90bb21b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java#L218-L254","documentation":"Thrown during discovery resolution when a single DiscoverySelector resolves to more than one TestDescriptor match. The engine's resolver contract expects a selector to identify at most one descriptor when used in a parent-resolving context, so an ambiguous match is a hard error. The message lists every matched descriptor so you can see exactly what collided.","triggerScenarios":"Calling Context.resolve(selector) / addToParent with a selector that an engine's discovery resolves to 2+ matches. Happens with custom DiscoverySelectors whose resolver is too broad, UniqueId selectors that match across engines, or ClassSelector/MethodSelector reused where duplicate descriptors exist. Also triggered by programmatic discovery via LauncherDiscoveryRequestBuilder when the same logical test is registered twice under the same selector.","commonSituations":"Two test engines both resolve the same selector (e.g. junit-jupiter and junit-vintage both claim a class); a custom TestEngine's SelectorResolver returns multiple Match objects instead of a single one; duplicate @Suite / @SelectClasses registrations that collapse onto one UniqueId; migration from JUnit 4 where a class is picked up by both vintage and jupiter engines.","solutions":["Read the listed descriptors: if two different engines claim the test, restrict discovery with an EngineFilter.includeEngines(...) to just one engine.","If a custom SelectorResolver is involved, make it return exactly one Match (use Match.exact) instead of a collection, or merge the descriptors.","Narrow the selector: use a UniqueIdSelector built from the full UniqueId rather than a broad ClassSelector/MethodSelector.","Remove the duplicate registration (e.g. a class listed twice in @SelectClasses, or a @Suite and a direct run overlapping)."],"exampleFix":"// before: broad selector resolves to multiple descriptors\nrequest = LauncherDiscoveryRequestBuilder.request()\n    .selectors(selectClass(MyTests.class)).build();\n// after: pin to a single unique id / single engine\nrequest = LauncherDiscoveryRequestBuilder.request()\n    .selectors(selectUniqueId(UniqueId.forEngine(\"junit-jupiter\")\n        .append(\"class\", MyTests.class.getName())))\n    .filters(includeEngines(\"junit-jupiter\")).build();","handlingStrategy":"validation","validationCode":"// Before resolving, restrict to a single engine and prefer unique selectors\nvar request = LauncherDiscoveryRequestBuilder.request()\n    .selectors(selectUniqueId(fullUniqueId))\n    .filters(includeEngines(\"junit-jupiter\"))\n    .build();\n// For custom resolvers, return Match.exact(descriptor) not a list.","typeGuard":"// narrowing is on the engine-internal Resolution; guard at the selector level\nstatic boolean isUnique(Resolution r) {\n    return r != null && r.getMatches().size() == 1;\n}","tryCatchPattern":"try {\n    context.resolve(selector);\n} catch (JUnitException e) {\n    if (e.getMessage().contains(\"did not yield unique test descriptor\")) {\n        // narrow selector or restrict engines, then retry discovery\n    }\n    throw e;\n}","preventionTips":["Pin discovery to one engine via includeEngines when running classes that multiple engines could claim.","Prefer UniqueIdSelector over ClassSelector/MethodSelector for programmatic runs.","Custom SelectorResolvers should return a single Match, never a multi-element collection."],"tags":["discovery","selector","ambiguous-match","test-engine"],"backgroundTag":null,"analyzedSha":"f070c699a08b5d8393df9afd147af5c5e90bb21b","analyzedAt":"2026-08-11T20:31:00.530Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}