vitest-dev/vitest · error · TypeError

Asymmetric matcher ${val.constructor.name} does not implemen

Error message

Asymmetric matcher ${val.constructor.name} does not implement toAsymmetricMatcher()

What it means

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.

Source

Thrown at packages/pretty-format/src/plugins/AsymmetricMatcher.ts:84

      stringedValue
      + SPACE
      + printer(val.sample, config, indentation, depth, refs)
    )
  }

  if (
    stringedValue === 'StringContaining'
    || stringedValue === 'StringNotContaining'
  ) {
    return (
      stringedValue
      + SPACE
      + printer(val.sample, config, indentation, depth, refs)
    )
  }

  if (typeof val.toAsymmetricMatcher !== 'function') {
    throw new TypeError(
      `Asymmetric matcher ${val.constructor.name} does not implement toAsymmetricMatcher()`,
    )
  }

  return val.toAsymmetricMatcher()
}

const test: NewPlugin['test'] = (val: any) =>
  val && val.$$typeof === asymmetricMatcher

const plugin: NewPlugin = { serialize, test }

export default plugin

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Implement `toAsymmetricMatcher()` on your matcher class, returning a display string (e.g. `StringContaining<...>`).
  2. Or override `toString()` to return one of the recognized names if its semantics match (ObjectContaining, ArrayContaining, StringMatching, StringContaining, and their Not variants).
  3. Avoid putting custom matchers into snapshots; assert against plain values instead.

Example fix

// before
class LengthMatcher {
  $$typeof = Symbol.for('jest.asymmetricMatcher')
  constructor(public sample: number) {}
}

// after
class LengthMatcher {
  $$typeof = Symbol.for('jest.asymmetricMatcher')
  constructor(public sample: number) {}
  toAsymmetricMatcher() {
    return `Length<${this.sample}>`
  }
}
Defensive patterns

Strategy: type-guard

Validate before calling

const ASYM = typeof Symbol === 'function' && Symbol.for ? Symbol.for('jest.asymmetricMatcher') : 0x13_57_A5

function isSerializableMatcher(v: any): boolean {
  if (!v || v.$$typeof !== ASYM) return true // not a matcher, plugin won't touch it
  const known = ['ArrayContaining', 'ArrayNotContaining', 'ObjectContaining', 'ObjectNotContaining', 'StringMatching', 'StringNotMatching', 'StringContaining', 'StringNotContaining']
  return known.includes(v.toString()) || typeof v.toAsymmetricMatcher === 'function'
}

Type guard

function isAsymmetricMatcher(v: unknown): v is { toAsymmetricMatcher: () => string; constructor: { name: string } } {
  return v != null
    && (v as any).$$typeof === (typeof Symbol !== 'undefined' && Symbol.for ? Symbol.for('jest.asymmetricMatcher') : 0x13_57_A5)
    && typeof (v as any).toAsymmetricMatcher === 'function'
}

Prevention

When it happens

Trigger: 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.

Common situations: Custom matchers from `expect.extend`; third-party matcher libraries; snapshotting `expect.customThing()` in a matcher error path.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/b57192d1b271104e.json. Report an issue: GitHub.