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
- 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.
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
- 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.
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
- Expected is not a string
- You must provide an object to ${this.toString()}, not '${typ
- You must provide an array to ${this.toString()}, not '${type
- any() expects to be passed a constructor function. Please pa
- Expected is not a String or a RegExp
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/b57192d1b271104e.json.
Report an issue: GitHub.