skip to content

Jest and Vitest both offer external snapshots stored in a companion file (toMatchSnapshot) and inline snapshots written back into the test file (toMatchInlineSnapshot). What does each form buy you, and how would you choose between them?

level: middleimportance: should knowfreq 40%

answer

  1. where does the expectation live?
  2. locality and review pressure
  3. the .snap nobody opens
  4. too big to inline is a smell
  5. orphans outlive their tests

basics

~20 s

An inline snapshot puts the expected value in the test file, so a reader sees the assertion and its expectation together and a reviewer cannot miss a change. An external file handles values too large to inline, at the cost of living where nobody reads it. Prefer inline whenever the value is small.

solid answer

~40 s

The difference is where the expectation lives, and therefore who reads it. An inline snapshot is committed inside the test, so the test remains self-documenting — you open it and immediately see what the output is supposed to be — and any change shows up in the diff of the file the reviewer is already reading. An external `.snap` file scales to values too large to sit inline, but that is also its problem: a large auto-generated file in a `__snapshots__` directory gets skimmed or collapsed in review, so changes slip through unread. My rule is that if the value is small enough to inline, inline it; if it is too big to inline, that is usually a signal the capture is too broad rather than a reason to move it into a file.

code

javascript · 14 lines
javascript
import { render, screen } from '@testing-library/react';
import { test, expect } from 'vitest';
import { Price } from './Price';

test('formats cents as currency', () => {
  render(<Price cents={1999} />);
  const el = screen.getByTestId('price');

  // External: the expected value lives in __snapshots__/Price.test.jsx.snap
  expect(el).toMatchSnapshot();

  // Inline: the expected value is committed here, visible in review
  expect(el.textContent).toMatchInlineSnapshot(`"$19.99"`);
});

go deeper

for a junior

Know that both forms compare against a stored baseline and differ only in where that baseline is kept — beside the assertion, or in a companion snapshot file.

for a middle

Explain the review consequence: an inline expectation is read as part of the test, while a generated file is easy to approve unseen. Mention that inline snapshots impose a natural size limit on what you capture.

for a senior

Show that you read "too large to inline" as a scoping signal rather than a storage problem, and that you have a convention for how a changed baseline is justified in a pull request.

for a principal

Turn it into a default the whole codebase follows: inline for anything reviewable, external reserved for named categories such as generated artefacts, with ownership on those directories so baseline changes reach a reviewer who cares.

## The mechanical difference Both forms compare a serialized value against a stored baseline. They differ only in *where* the baseline is stored. - `expect(value).toMatchSnapshot()` stores the text in a companion file, conventionally `__snapshots__/<testfile>.snap`, keyed by test name. - `expect(value).toMatchInlineSnapshot()` stores the text as an argument inside the test file. The first run rewrites your source, filling the empty call with the recorded value. ```javascript // after the first run, the runner has written the expected value into the call expect(formatPrice(1999)).toMatchInlineSnapshot(`"$19.99"`); ``` Everything else — first-run recording, diff-on-mismatch, deliberate update — behaves the same way. ## What inline snapshots buy **Locality.** The assertion and its expectation sit on one line. Someone reading the test learns what the code produces without opening a second file. That makes an inline snapshot function much more like a hand-written assertion than like a recording. **Review pressure.** A changed expectation appears in the diff of the test file, surrounded by the test's name and setup. Reviewers who would scroll past a `.snap` hunk stop and read it, because it is in code they are already reviewing. **Natural size limit.** You cannot inline three hundred lines of markup without the test becoming unreadable, and that discomfort is useful feedback. Inline snapshots push you toward capturing small values. ## What external snapshots buy **Capacity.** Some values genuinely are large — a generated stylesheet, a serialized document, a big fixture transformation — and a file keeps the test readable. **Test-file stability.** Your test source is never rewritten by the runner, which some teams prefer for tooling or formatting reasons. The cost is attention. A `__snapshots__` file is auto-generated, often marked as generated by review tooling, and easy to approve unread. The whole failure mode of snapshot testing — baselines updated without anyone judging them — lives in that gap. ## Choosing A workable default: 1. **Small serializable value → inline.** Formatted strings, small objects, a compact array derived from the UI, a short subtree's text content. 2. **Large value → ask why it is large before reaching for a file.** In component tests, "too large to inline" usually means you are capturing an entire page rather than the thing the test is about. Narrowing the capture is a better fix than relocating the baseline. 3. **Genuinely large and genuinely worth guarding → external file**, plus a review convention that someone reads the diff. Generated artefacts are the honest case here. ## The trap to name in an interview Inline snapshots are safer for review but they also make careless updating physically easier — the fix is one keystroke away in the file you already have open. Neither form protects you from the reflex of regenerating a red baseline without reading it. The protection is process: a small enough value that the diff is judgeable, and a rule that a changed baseline is explained in the PR description like any other behaviour change. One more practical note: with either form, a renamed or deleted test leaves its stored baseline behind. External files accumulate orphaned entries that runners report as obsolete and can prune on a deliberate update run; inline snapshots disappear with the test that held them, which is a quiet advantage of keeping the expectation inside the test.

  • Inline snapshots rewrite your test source on the first run. Is that a problem?
    It is a tradeoff, not a defect. The rewrite is what makes the expectation visible in the file, and the change is committed like any other source edit. The care needed is that a careless update overwrites the expectation in place, so the diff must still be read. Teams that dislike source rewriting can hand-write the expected value instead — at which point it is just a normal assertion.
  • What happens to a stored baseline when its test is renamed?
    An external snapshot is keyed by test name, so a rename orphans the old entry: it stays in the file, is reported as obsolete, and is removed only on a deliberate update run. An inline snapshot has no such problem because it lives in the test and moves or disappears with it. Orphans are harmless but they accumulate and add noise to the file.
  • You inherit a test whose only assertion is a 200-line external snapshot. What do you do?
    Read what the test claims to prove from its name, then write the explicit assertions for that claim. Once those exist, either delete the snapshot or narrow it to a small derived value worth guarding — for example the list of rendered row values, inlined. The goal is that the test states its intent in the file, rather than deferring it to a generated artefact.

saying these in an interview costs you the question

  • Thinks inline and external snapshots differ in what they compare
  • Says external files are better because they keep tests short
  • Assumes reviewers read generated snapshot files carefully
  • Reaches for an external file because the value is huge, without asking why
  • Believes inline snapshots must be hand-written by the developer

context