{"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/956246301e03d757539f7b76dd5a91f298637563/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java#L218-L254","documentation":"Thrown by EngineDiscoveryRequestResolution.DefaultContext.resolve (line 226-245) when a nested selector resolves to more than one Match. The discovery model expects that resolving a selector within a parent context yields at most one TestDescriptor; multiple matches indicate ambiguity. The exception lists the colliding descriptors so the user can see what overlapped.","triggerScenarios":"A SelectorResolver registered by a TestEngine returns a Resolution with multiple exact Match entries for a single selector, and EngineDiscoveryRequestResolution.DefaultContext.resolve() (used by addToParent that needs a unique parent) cannot pick one. Typically happens with custom resolvers or when two test descriptors collide on the same UniqueId/selector.","commonSituations":"Writing a custom SelectorResolver that over-resolves (returns multiple matches for a method/class selector); duplicate classes on the classpath so a class selector matches twice; overloaded methods where a resolver returns broad matches; an engine bug producing non-unique UniqueIds.","solutions":["If you author a SelectorResolver, ensure resolve(...) returns a Resolution with a single exact match when the engine calls into a context that requires uniqueness (use Match.exact for one descriptor).","Eliminate duplicate classes/resources on the classpath that cause a single selector to match multiple descriptors.","Disambiguate the selector: use MethodSelector with explicit parameter types or a UniqueIdSelector instead of a broad ClassSelector/PackageSelector.","Read the listed descriptors in the message to identify which two elements collide, then fix their UniqueIds or discovery logic."],"exampleFix":"// before: custom resolver returns multiple matches\n@Override public Resolution resolve(ClassSelector sel, Context ctx) {\n    return Resolution.match(Match.exact(d1)).match(Match.exact(d2)); // ambiguous\n}\n\n// after: resolve to exactly one descriptor\n@Override public Resolution resolve(ClassSelector sel, Context ctx) {\n    TestDescriptor unique = resolveOne(sel);\n    return Resolution.match(Match.exact(unique));\n}","handlingStrategy":"validation","validationCode":"// When authoring a SelectorResolver, never return multiple exact matches from a\n// context that requires a unique parent. Resolve to exactly one descriptor:\n@Override public Resolution resolve(MethodSelector sel, Context ctx) {\n    Optional<TestDescriptor> one = findSingle(sel);\n    return one.map(d -> Resolution.match(Match.exact(d))).orElseGet(Resolution::unresolved);\n}","typeGuard":null,"tryCatchPattern":"try {\n    context.resolve(mySelector);\n} catch (JUnitException e) {\n    // message lists the colliding descriptors; use them to disambiguate\n    log.error(\"ambiguous resolution: {}\", e.getMessage());\n    throw e;\n}","preventionTips":["Ensure SelectorResolver implementations return a single exact Match where uniqueness is required.","Disambiguate with UniqueIdSelector or MethodSelector including parameter types.","Eliminate duplicate classes on the classpath."],"tags":["junit-platform","discovery","selector-resolver","test-descriptor","engine-internal"],"analyzedSha":"956246301e03d757539f7b76dd5a91f298637563","analyzedAt":"2026-08-04T19:25:17.049Z","schemaVersion":2}