Skip to content

Incomplete diff of toMatchSnapshot(properties) mismatch error #9985

@hi-ogawa

Description

@hi-ogawa

Describe the bug

(not sure how to describe, so 🤖's best shot. repro is mine though.)

When toMatchSnapshot(properties) fails, the diff misleadingly shows fields like x as additions, as if they shouldn't be there. But x is a valid part of the object — the only actual problem is that y changed from 5 to 10. The diff should focus on the mismatched values, not imply that extra fields are wrong.

  • repro.test.ts
import { expect, test } from 'vitest';

test('example', () => {
  // initially snapshot created with
  // { x: 'hello', y: 5 }
  expect({ x: 'world', y: 10 }).toMatchSnapshot({ y: 5 });
});

Test run:

 FAIL  test/repro.test.ts > example
Error: Snapshot properties mismatched

- Expected
+ Received

  {
-   "y": 5,
+   "x": "world",
+   "y": 10,
  }

 ❯ test/repro.test.ts:6:33
      4|   // initially snapshot created with
      5|   // { x: 'hello', y: 5 }
      6|   expect({ x: 'world', y: 10 }).toMatchSnapshot({ y: 5 });
       |                                 ^
      7| });
      8|
 ❯ new Promise ../../../blitz.4c73681d.js:31:30606
🤖 Root cause and suggestions

In packages/snapshot/src/client.ts, the properties mismatch error passes received and properties as actual/expected:

if (!propertiesPass) {
  return {
    pass: false,
    message: () => 'Snapshot properties mismatched',
    actual: received,      // full object
    expected: properties,  // subset — missing fields show as additions
  }
}

The diff is generated downstream by processError (packages/utils/src/error.ts), which just calls printDiffOrStringify(err.actual, err.expected). It has no knowledge of snapshot semantics — it faithfully diffs the two values it's given. So the fix needs to happen at the source, not in the diff renderer.

This is inherently tricky because the properties check is a subset equality — the user intentionally provides fewer keys than the full object. There's no "correct full expected" available at this point (the previous snapshot isn't loaded yet since snapshotState.match() hasn't been called).

Possible approaches:

  • Filter actual to only the keys in properties — diff becomes { length: 11 } vs { length: 6 }. Clean and focused on what the user cares about, but loses context of the full object.
  • Load the previous snapshot first to construct a full expected value — more accurate but adds complexity and couples the properties check to snapshot state.

Reproduction

https://stackblitz.com/edit/vitest-dev-vitest-xnjrwzha?file=test%2Frepro.test.ts

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 22.22.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.8.2 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/bin/pnpm
  npmPackages:
    @vitest/ui: latest => 4.1.2 
    vite: latest => 8.0.3 
    vitest: latest => 4.1.2

Used Package Manager

npm

Validations

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions