Skip to content

Commit 6b87906

Browse files
authored
test: update unit test guidelines to account for the removal of toMatchSnapshot (#28499)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** > Adds a **mandatory snapshot testing policy** that bans `toMatchSnapshot()` (and new `.snap` files) and explicitly allows `toMatchInlineSnapshot()` only when the serialized output is the real assertion. > > Updates reviewer/author workflow guidance to *flag and migrate* existing `toMatchSnapshot()` usages when touching tests, and extends the unit test runner instructions to detect and report banned snapshot calls. > ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk because changes are documentation-only, affecting developer workflow/review expectations but not runtime code. The main risk is process friction if teams rely on external `.snap` snapshots. > > **Overview** > Adds a **mandatory snapshot testing policy** that bans `toMatchSnapshot()` (and new `.snap` files) and explicitly allows `toMatchInlineSnapshot()` only when the serialized output is the real assertion. > > Updates reviewer/author workflow guidance to *flag and migrate* existing `toMatchSnapshot()` usages when touching tests, and extends the unit test runner instructions to detect and report banned snapshot calls. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 407c55d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0a229a8 commit 6b87906

2 files changed

Lines changed: 70 additions & 11 deletions

File tree

.claude/commands/unit-test.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,18 @@
55
## Steps
66

77
1. **Get changed files**
8-
98
- Run `git diff --name-only --diff-filter=ACMR` for all changes (staged + unstaged)
109
- Filter for `.js`, `.jsx`, `.ts`, `.tsx` files
1110
- Display list
1211

1312
2. **Find test files**
14-
1513
- Check if file is already a test file (contains `.test.`)
1614
- For source files, find related tests: `{basename}.test.{ext}`
1715
- Exclude snapshots (`.snap`)
1816
- Collect unique test files
17+
- **Flag any `toMatchSnapshot()` calls found** — these are banned; note them for migration to explicit assertions or `toMatchInlineSnapshot()`
1918

2019
3. **Run tests**
21-
2220
- Execute `yarn jest` with all found test files
2321
- Run without coverage
2422
- Capture results

.cursor/rules/unit-testing-guidelines.mdc

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,62 @@ testID={`token-item-${token.symbol}`}
173173
testID={`network-option-${network.chainId}`}
174174
```
175175

176+
## Snapshot Testing Policy - MANDATORY
177+
178+
### The Rule
179+
180+
- ❌ **`toMatchSnapshot()` is BANNED** — Do not use it. Do not add it. Do not approve it.
181+
- ✅ **`toMatchInlineSnapshot()` is ALLOWED** — Use sparingly, only when the serialized output is the meaningful assertion.
182+
183+
### Why `toMatchSnapshot()` Is Banned
184+
185+
External snapshot files (`.snap`) have three critical problems:
186+
187+
1. **Invisible in review** — The diff lives in a separate `.snap` file that reviewers routinely rubber-stamp. Regressions hide there.
188+
2. **No code ownership** — `CODEOWNERS` explicitly assigns `**/*.snap` to *nobody*, meaning no team is accountable for snapshot correctness.
189+
3. **Brittle by design** — Any style tweak, whitespace change, or unrelated refactor regenerates the snapshot and silently passes CI.
190+
191+
### Why `toMatchInlineSnapshot()` Is Allowed
192+
193+
The snapshot string lives directly in the test file, so:
194+
- It appears in the PR diff alongside the code that produces it
195+
- The file's code owner is responsible for reviewing it
196+
- Reviewers can see exactly what changed and why
197+
198+
### When to Use `toMatchInlineSnapshot()`
199+
200+
Only use it when the serialized shape of the output **is** the assertion — for example, verifying a complex object structure, a formatted string, or a serialized data payload. Do **not** use it as a lazy substitute for explicit `expect` calls.
201+
202+
```ts
203+
// ❌ BANNED — writes to an external .snap file
204+
expect(tree).toMatchSnapshot();
205+
expect(component).toMatchSnapshot();
206+
expect(result).toMatchSnapshot();
207+
208+
// ✅ ALLOWED — snapshot is inline and visible in review
209+
expect(result).toMatchInlineSnapshot(`
210+
{
211+
"chainId": "0x1",
212+
"name": "Ethereum Mainnet",
213+
}
214+
`);
215+
216+
// ✅ PREFERRED — explicit assertions are always better than snapshots
217+
expect(result.chainId).toBe('0x1');
218+
expect(result.name).toBe('Ethereum Mainnet');
219+
```
220+
221+
### Migrating Existing `toMatchSnapshot()` Calls
222+
223+
When you encounter an existing `toMatchSnapshot()` call:
224+
1. **Prefer replacing it** with explicit `expect` assertions targeting the specific values that matter.
225+
2. If the full serialized output is genuinely meaningful, convert to `toMatchInlineSnapshot()`.
226+
3. Delete the corresponding `.snap` file entry once migrated.
227+
228+
Do **not** leave `toMatchSnapshot()` in place when modifying a test file — migrate it as part of your change.
229+
230+
---
231+
176232
## Assertions - PREFER toBeOnTheScreen
177233

178234
- **ALWAYS use `toBeOnTheScreen()`** to assert element presence - NOT `toBeTruthy()` or `toBeDefined()`
@@ -398,7 +454,7 @@ it.each(['small', 'medium', 'large'] as const)('renders %s size', (size) => {
398454
## Test Determinism
399455

400456
- **EVERYTHING** not under test must be mocked - no exceptions.
401-
- Avoid brittle tests: do not test internal state or UI snapshots for logic.
457+
- Avoid brittle tests: do not test internal state or UI snapshots for logic. **`toMatchSnapshot()` is banned** — see Snapshot Testing Policy above.
402458
- Only test public behavior, not implementation details.
403459
- Mock time, randomness, and external systems to ensure consistent results.
404460

@@ -514,7 +570,7 @@ expect(result).toBe(false);
514570
```
515571

516572
- Ensure tests use proper matchers (`toBeOnTheScreen` vs `toBeDefined`).
517-
- Do not approve PRs without reviewing snapshot diffs, it can reveal errors.
573+
- **Reject any new `.snap` files or new `toMatchSnapshot()` calls** — these are banned; require the author to use explicit assertions or `toMatchInlineSnapshot()` instead.
518574
- Reject tests with complex names combining multiple logical conditions (AND/OR).
519575

520576
# Refactoring Support
@@ -527,6 +583,7 @@ expect(result).toBe(false);
527583

528584
Before submitting any test file, verify:
529585

586+
- [ ] **No `toMatchSnapshot()` calls** — BANNED; use explicit assertions or `toMatchInlineSnapshot()` instead
530587
- [ ] **No mocking to inject testIDs** - Use component's built-in testID support
531588
- [ ] **testIDs via child prop objects** - Use `closeButtonProps={{ testID }}` not mocks
532589
- [ ] **No "should" in any test name**
@@ -545,6 +602,7 @@ Before submitting any test file, verify:
545602

546603
# Common Mistakes to AVOID - CRITICAL
547604

605+
- ❌ **Using `toMatchSnapshot()`** — BANNED; it writes opaque `.snap` files with no code owner; use explicit assertions or `toMatchInlineSnapshot()` instead
548606
- ❌ **Mocking to inject testIDs** - Components already support testID (see guidelines above)
549607
- ❌ **Using "should" in test names** - This is the #1 mistake, use action-oriented descriptions
550608
- ❌ **Testing multiple behaviors in one test** - One test, one behavior
@@ -588,8 +646,9 @@ yarn test:unit:coverage
588646
## Workflow Requirements
589647

590648
- Confirm all tests are passing before commit.
591-
- When a snapshot update is detected, confirm the changes are expected.
592-
- Do not blindly update snapshots without understanding the differences.
649+
- **Do not add new `toMatchSnapshot()` calls** — this is banned. See Snapshot Testing Policy.
650+
- When modifying a test file that contains `toMatchSnapshot()`, opportunistically migrate those calls to explicit assertions or `toMatchInlineSnapshot()` — do not block a PR solely to force migration, but do not add new ones.
651+
- `toMatchInlineSnapshot()` updates are acceptable but must be reviewed — confirm the new inline snapshot reflects an intentional, expected change.
593652

594653
# Reference Code Examples
595654

@@ -606,12 +665,14 @@ it('indicates expired milk when past due date', () => {
606665
});
607666
```
608667

609-
## ❌ Brittle Snapshot
668+
## ❌ Banned: `toMatchSnapshot()`
610669

611670
```ts
671+
// ❌ BANNED — toMatchSnapshot() writes to an external .snap file.
672+
// It has no code owner, hides regressions, and breaks on trivial changes.
612673
it('renders the button', () => {
613674
const { container } = render(<MyButton />);
614-
expect(container).toMatchSnapshot(); // 🚫 fails on minor style changes
675+
expect(container).toMatchSnapshot(); // 🚫 BANNED — do not use
615676
});
616677
```
617678

@@ -640,7 +701,7 @@ it('hides selector when disabled', () => {
640701

641702
## Reviewer Responsibilities
642703

643-
Validate tests fail when code breaks • Ensure proper matchers • Review snapshot diffs • Reject complex names with AND/OR
704+
Validate tests fail when code breaks • Ensure proper matchers • **Reject new `toMatchSnapshot()` calls and new `.snap` files** • Reject complex names with AND/OR
644705

645706
```ts
646707
// OK
@@ -652,6 +713,6 @@ it('renders and disables button when input is empty or missing required field');
652713

653714
## Workflow
654715

655-
Always run tests after changes • Confirm all pass before commit • Review snapshot changes • Don't blindly update snapshots
716+
Always run tests after changes • Confirm all pass before commit • **Do not add new `toMatchSnapshot()` calls** — migrate existing ones to explicit assertions or `toMatchInlineSnapshot()` when touching a test file
656717

657718
**Resources**: [Contributor docs](https://github.com/MetaMask/contributor-docs/blob/main/docs/testing/unit-testing.md) • [Jest Matchers](https://jestjs.io/docs/using-matchers) • [React Native Testing Library](https://testing-library.com/docs/react-native-testing-library/intro/)

0 commit comments

Comments
 (0)