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

  1. Ensure the received value passed to expect() is a non-null object before using the properties overload.
  2. Drop the properties argument if you intended the second arg to be a snapshot name/message.
  3. Wrap the assertion so it only runs when the value is an object: if (typeof value === 'object' && value) expect(value).toMatchSnapshot({...}).
  4. 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

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


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