{"id":"b57192d1b271104e","repo":"vitest-dev/vitest","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":84,"sourceCode":"      stringedValue\n      + SPACE\n      + printer(val.sample, config, indentation, depth, refs)\n    )\n  }\n\n  if (\n    stringedValue === 'StringContaining'\n    || stringedValue === 'StringNotContaining'\n  ) {\n    return (\n      stringedValue\n      + SPACE\n      + printer(val.sample, config, indentation, depth, 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\nconst test: NewPlugin['test'] = (val: any) =>\n  val && val.$$typeof === asymmetricMatcher\n\nconst plugin: NewPlugin = { serialize, test }\n\nexport default plugin\n","sourceCodeStart":66,"sourceCodeEnd":98,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/pretty-format/src/plugins/AsymmetricMatcher.ts#L66-L98","documentation":"The AsymmetricMatcher plugin formats jest-style matchers tagged with `$$typeof === Symbol.for('jest.asymmetricMatcher')`. It special-cases the built-ins (ObjectContaining, ArrayContaining, StringMatching, etc.) by their `toString()`; for any other matcher it calls `val.toAsymmetricMatcher()`. A custom matcher that sets the symbol but omits that method cannot be serialized and is rejected with the constructor name.","triggerScenarios":"Snapshotting or diffing a failed assertion whose expected value is a custom asymmetric matcher (via `expect.extend` or hand-rolled with the `$$typeof` symbol) that lacks a `toAsymmetricMatcher()` method and whose `toString()` is not one of the recognized built-in names.","commonSituations":"Custom matchers from `expect.extend`; third-party matcher libraries; snapshotting `expect.customThing()` in a matcher error path.","solutions":["Implement `toAsymmetricMatcher()` on your matcher class, returning a display string (e.g. `StringContaining<...>`).","Or override `toString()` to return one of the recognized names if its semantics match (ObjectContaining, ArrayContaining, StringMatching, StringContaining, and their Not variants).","Avoid putting custom matchers into snapshots; assert against plain values instead."],"exampleFix":"// before\nclass LengthMatcher {\n  $$typeof = Symbol.for('jest.asymmetricMatcher')\n  constructor(public sample: number) {}\n}\n\n// after\nclass LengthMatcher {\n  $$typeof = Symbol.for('jest.asymmetricMatcher')\n  constructor(public sample: number) {}\n  toAsymmetricMatcher() {\n    return `Length<${this.sample}>`\n  }\n}","handlingStrategy":"type-guard","validationCode":"const ASYM = typeof Symbol === 'function' && Symbol.for ? Symbol.for('jest.asymmetricMatcher') : 0x13_57_A5\n\nfunction isSerializableMatcher(v: any): boolean {\n  if (!v || v.$$typeof !== ASYM) return true // not a matcher, plugin won't touch it\n  const known = ['ArrayContaining', 'ArrayNotContaining', 'ObjectContaining', 'ObjectNotContaining', 'StringMatching', 'StringNotMatching', 'StringContaining', 'StringNotContaining']\n  return known.includes(v.toString()) || typeof v.toAsymmetricMatcher === 'function'\n}","typeGuard":"function isAsymmetricMatcher(v: unknown): v is { toAsymmetricMatcher: () => string; constructor: { name: string } } {\n  return v != null\n    && (v as any).$$typeof === (typeof Symbol !== 'undefined' && Symbol.for ? Symbol.for('jest.asymmetricMatcher') : 0x13_57_A5)\n    && typeof (v as any).toAsymmetricMatcher === 'function'\n}","tryCatchPattern":null,"preventionTips":["Always implement `toAsymmetricMatcher()` on custom asymmetric matchers.","In `expect.extend`, return matchers that are self-describing.","Avoid snapshotting custom matchers; assert on resolved values instead."],"tags":["pretty-format","asymmetric-matcher","expect","plugins"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}