vitest-dev/vitest · error · Error
Received value must be an object when the matcher has proper
Error message
Received value must be an object when the matcher has properties
What it means
Thrown by SnapshotClient.match() when the second 'properties' argument is provided (an object of asymmetric matchers / expected subset) but the received value is not a non-null object. Vitest needs an object to deep-merge the properties hint against the received value before serializing; primitives and null/undefined cannot be subset-matched. The library throws rather than silently producing a misleading snapshot.
Source
Thrown at packages/snapshot/src/client.ts:151
if (!filepath) {
throw new Error('Snapshot cannot be used outside of test')
}
const snapshotState = this.getSnapshotState(filepath)
const testName = [name, ...(message ? [message] : [])].join(' > ')
// Probe first so we can mark as checked even on early return
const expectedSnapshot = snapshotState.probeExpectedSnapshot({
testName,
testId,
isInline,
inlineSnapshot,
})
if (typeof properties === 'object') {
if (typeof received !== 'object' || !received) {
expectedSnapshot.markAsChecked()
throw new Error(
'Received value must be an object when the matcher has properties',
)
}
let propertiesPass: boolean
try {
propertiesPass = this.options.isEqual?.(received, properties) ?? false
}
catch (err) {
expectedSnapshot.markAsChecked()
throw err
}
if (!propertiesPass) {
expectedSnapshot.markAsChecked()
return {
pass: false,
message: () => errorMessage || 'Snapshot properties mismatched',
actual: received,View on GitHub (pinned to d568f8ce37)
Solutions
- Ensure the received value passed to expect() is a non-null object before using the properties overload.
- Drop the properties argument if you intended the second arg to be a snapshot name/message.
- Wrap the assertion so it only runs when the value is an object: if (typeof value === 'object' && value) expect(value).toMatchSnapshot({...}).
- Update the test to reflect the new return type of the code under test.
Example fix
// before
expect(getName()).toMatchSnapshot({ length: expect.any(Number) })
// after
expect(getName()).toMatchSnapshot() // getName() returns a string Defensive patterns
Strategy: type-guard
Validate before calling
const isObj = (v: unknown): v is Record<string, unknown> =>
typeof v === 'object' && v !== null
if (isObj(received)) {
expect(received).toMatchSnapshot({ id: expect.any(Number) })
} else {
expect(received).toMatchSnapshot()
} Type guard
const isPlainObject = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)
Prevention
- Only pass the properties hint when the received value is statically known to be an object.
- Reserve the second argument of toMatchSnapshot for object subset matching; use the string overload for naming.
- Add a TypeScript helper that constrains the properties overload to object receivers.
When it happens
Trigger: expect('string').toMatchSnapshot({ length: 5 }); expect(null).toMatchSnapshot({ foo: expect.any(String) }); expect(42).toMatchSnapshot({}); passing a properties hint to toMatchSnapshot while the received value is a primitive, null, or undefined.
Common situations: Refactor that changed a function return from object to string but kept the properties matcher; using the properties overload by mistake (passing a second argument intended as a message); testing JSON that parsed to a non-object.
Related errors
- expected must be a function, received ${typeof expected}
- snapshot function didn't throw
- '${getAssertionName(obj)}' cannot be used without test conte
- aria adapter expects an Element
- Access denied to "${path}". See Vite config documentation fo
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/969c3d0e44c59227.json.
Report an issue: GitHub.