{"id":"9094e629763e224e","repo":"jestjs/jest","slug":"asymmetric-matcher-val-constructor-name-does-no","errorCode":null,"errorMessage":"Asymmetric matcher ${val.constructor.name} does not implement toAsymmetricMatcher()","messagePattern":"Asymmetric matcher (.+?) does not implement toAsymmetricMatcher\\(\\)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/pretty-format/src/plugins/AsymmetricMatcher.ts","lineNumber":97,"sourceCode":"      printer(val.sample, config, indentation, depth, refs)\n    );\n  }\n\n  if (stringedValue === 'ArrayOf' || stringedValue === 'NotArrayOf') {\n    if (++depth > config.maxDepth) {\n      return `[${stringedValue}]`;\n    }\n    return `${stringedValue + SPACE}${printer(\n      val.sample,\n      config,\n      indentation,\n      depth,\n      refs,\n    )}`;\n  }\n\n  if (typeof val.toAsymmetricMatcher !== 'function') {\n    throw new TypeError(\n      `Asymmetric matcher ${val.constructor.name} does not implement toAsymmetricMatcher()`,\n    );\n  }\n\n  return val.toAsymmetricMatcher();\n};\n\nexport const test: NewPlugin['test'] = (val: any) =>\n  val && val.$$typeof === asymmetricMatcher;\n\nconst plugin: NewPlugin = {serialize, test};\n\nexport default plugin;\n","sourceCodeStart":79,"sourceCodeEnd":111,"githubUrl":"https://github.com/jestjs/jest/blob/f49721c78e195558b40913977c9230f5b7f559d8/packages/pretty-format/src/plugins/AsymmetricMatcher.ts#L79-L111","documentation":"The AsymmetricMatcher plugin serializes any object whose $$typeof matches Symbol.for('jest.asymmetricMatcher'). After handling Jest's built-in matchers (ArrayContaining, ObjectContaining, StringMatching, StringContaining, ArrayOf, etc.) by their toString() value, the fallback at AsymmetricMatcher.ts:96 requires the object to implement toAsymmetricMatcher(). A custom matcher tagged with the asymmetricMatcher symbol but missing that method throws this TypeError.","triggerScenarios":"Creating a custom asymmetric matcher that sets `this.$$typeof = Symbol.for('jest.asymmetricMatcher')` (or extends a base that does) without implementing a `toAsymmetricMatcher()` method, then letting pretty-format snapshot/serialize it (e.g. via expect.extend with an improperly formed matcher, or expect.any-like helpers).","commonSituations":"Library authors building custom expect matchers, upgrading jest/pretty-format across versions where the matcher contract changed, or copy-pasting a matcher that relies on toString() names that no longer match the built-in branches so execution falls through to the toAsymmetricMatcher() requirement.","solutions":["Implement `toAsymmetricMatcher()` on the custom matcher class returning the desired string representation (e.g. `StringContaining <sample>`).","Ensure toString() returns one of the recognized built-in names if you intend to reuse built-in serialization (ArrayContaining, ObjectContaining, StringMatching, etc.).","Do not set $$typeof to the jest.asymmetricMatcher symbol unless you fully implement the matcher contract."],"exampleFix":"// before\nclass MyMatcher {\n  constructor(sample) { this.sample = sample; }\n  $$typeof = Symbol.for('jest.asymmetricMatcher');\n  asymmetricMatch(other) { return other === this.sample; }\n}\n// after\nclass MyMatcher {\n  constructor(sample) { this.sample = sample; }\n  $$typeof = Symbol.for('jest.asymmetricMatcher');\n  asymmetricMatch(other) { return other === this.sample; }\n  toAsymmetricMatcher() { return `MyMatcher ${this.sample}`; }\n}","handlingStrategy":"type-guard","validationCode":"function assertMatcherSerializable(matcher) {\n  if (matcher && matcher.$$typeof === Symbol.for('jest.asymmetricMatcher')) {\n    if (typeof matcher.toAsymmetricMatcher !== 'function' &&\n        !['ArrayContaining','ArrayNotContaining','ObjectContaining','ObjectNotContaining','StringMatching','StringNotMatching','StringContaining','StringNotContaining','ArrayOf','NotArrayOf'].includes(matcher.toString())) {\n      throw new TypeError(`${matcher.constructor.name} must implement toAsymmetricMatcher()`);\n    }\n  }\n}","typeGuard":"function isSerializableAsymmetricMatcher(val): boolean {\n  if (!val || val.$$typeof !== Symbol.for('jest.asymmetricMatcher')) return false;\n  return typeof val.toAsymmetricMatcher === 'function' ||\n    /^(ArrayContaining|ArrayNotContaining|ObjectContaining|ObjectNotContaining|StringMatching|StringNotMatching|StringContaining|StringNotContaining|ArrayOf|NotArrayOf)$/.test(val.toString());\n}","tryCatchPattern":null,"preventionTips":["Implement toAsymmetricMatcher() on every custom asymmetric matcher you tag with the jest.asymmetricMatcher symbol.","Avoid setting $$typeof to Symbol.for('jest.asymmetricMatcher') unless you implement the full matcher contract.","After upgrading pretty-format/jest, re-test custom matchers since the built-in toString() branch list can change."],"tags":["pretty-format","asymmetric-matcher","jest","plugin","serialization"],"analyzedSha":"f49721c78e195558b40913977c9230f5b7f559d8","analyzedAt":"2026-08-03T20:16:28.571Z","schemaVersion":2}