Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,134 @@ exports[`suite of snapshot tests > snapshot test 2`] = `
Once the snapshot file is created, run the tests again without the
`--test-update-snapshots` flag. The tests should pass now.

### Inline snapshots

<!-- YAML
added: REPLACEME
-->

> Stability: 1.0 - Early development

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docs rock!


Inline snapshots store the expected snapshot content directly within the test
file as a template literal, rather than in an external `.snapshot` file. This
makes it easier to see what the test expects and reduces the number of files in
your project. Inline snapshots are particularly useful for small snapshots
where having the expected value close to the test provides better readability.

The `t.assert.inlineSnapshot()` function creates inline snapshot assertions.
When called without a second argument, the function will insert the serialized
value as a template literal directly into your source code when run with the
[`--test-update-snapshots`][] flag.

```mjs
import { test } from 'node:test';

test('inline snapshot test', (t) => {
const user = { name: 'Alice', age: 30 };

// Initially call without second argument
t.assert.inlineSnapshot(user);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great name! I think this aligns really well to the current TestContext.assert.snapshot() API. Also, I'm pleasantly surprised I don't need to await here.

t.assert.inlineSnapshot('hello world');
});
```

```cjs
const { test } = require('node:test');

test('inline snapshot test', (t) => {
const user = { name: 'Alice', age: 30 };

// Initially call without second argument
t.assert.inlineSnapshot(user);
t.assert.inlineSnapshot('hello world');
});
```

When first run without the `--test-update-snapshots` flag, the test will fail

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, the ideal DX is to always assume a missing second parameter "wants to be updated" whether or not the --test-update-snapshots flag is passed. You can then get a really nice micro workflow on a given test without ever needing to pass the flag just by repeatedly deleting the param (in my IDE, I often delete the parameter just by hitting undo: ctrl-z, save, test / update, ctrl-z, save, test / update, ... until I get the shape I want).

with a "Missing inline snapshots" error. Run the test with
`--test-update-snapshots` to generate the snapshots:

```bash
node --test --test-update-snapshots test.js
```

After running with the update flag, your test file will be automatically
modified to include the snapshot content:

```mjs
import { test } from 'node:test';

test('inline snapshot test', (t) => {
const user = { name: 'Alice', age: 30 };

t.assert.inlineSnapshot(user, `
Copy link

@niedzielski niedzielski Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be really helpful if inlineSnapshot() supported wrapping. For example:

/**
 * Asserts bitmap is the expected hex value. The `expectedHex` arg is replaced
 * automatically when tests are run with `--test-update-snapshots`.
 */
function assertBitmapHex(ctx: TestContext, bmp: Uint8Array, expectedHex: string): string {
  const actualHex = [...bmp]
    .map(v => v.toString(16).padStart(2, '0'))
    .join('')
  ctx.assert.inlineSnapshot(actualHex, expectedHex) // <-- don't update me inline here!
}
test('', () => {
  // ...
  assertBitmapHex(bmp, '000000000000000000000000') // <-- I get updated inline here since I'm called at the top level test function.
})

It's possible to not use wrapping but then it gets pretty clumsy trying to fill out all the args in every test.

{
"name": "Alice",
"age": 30
}
`);
t.assert.inlineSnapshot('hello world', `
hello world
`);
});
```

```cjs
const { test } = require('node:test');

test('inline snapshot test', (t) => {
const user = { name: 'Alice', age: 30 };

t.assert.inlineSnapshot(user, `
{
"name": "Alice",
"age": 30
}
`);
t.assert.inlineSnapshot('hello world', `
hello world
`);
});
```

You can also provide an explicit second argument with a template literal
containing the expected snapshot. This is useful when you want to write the
expected value yourself or when updating specific snapshots:

```js
test('explicit inline snapshot', (t) => {
t.assert.inlineSnapshot({ status: 'ok' }, `
{
"status": "ok"
}
`);
});
```

If the inline snapshot content doesn't match the actual value, the test will
fail with a snapshot mismatch error. Use `--test-update-snapshots` to update
the snapshots to match the current values.

#### Inline snapshot limitations

Inline snapshots are currently in early development and have several limitations:

* **Custom serializers for primitives**: Custom serializers configured via
`setDefaultSnapshotSerializers()` are only applied to complex objects (arrays,
objects) but not to primitive values (strings, numbers, booleans, null,
undefined). Primitive values are serialized using their natural string
representation to maintain readability in the source code.

* **Destructured calls**: Inline snapshots are only detected when called as
`t.assert.inlineSnapshot()` or when destructured as
`const { inlineSnapshot } = t.assert; inlineSnapshot()`. Renamed destructured
calls like `const { inlineSnapshot: snap } = t.assert; snap()` are not
currently detected.

* **Template literal expressions**: The expected value must be a plain template
literal without embedded expressions. Template literals containing `${...}`
expressions will cause the test to fail.

## Test reporters

<!-- YAML
Expand Down Expand Up @@ -3557,6 +3685,55 @@ test('snapshot test with custom serialization', (t) => {
});
```

#### `context.assert.inlineSnapshot(value[, expected])`

<!-- YAML
added: v24.0.0
-->

> Stability: 1.0 - Early development

* `value` {any} A value to serialize to a string.
* `expected` {string} Optional template literal containing the expected
serialized value. If Node.js was started with the [`--test-update-snapshots`][]
flag, this argument is ignored and the test file is automatically updated with
the serialized value. If not provided and not in update mode, the test will
fail with a "Missing inline snapshots" error.

This function implements assertions for inline snapshot testing. Unlike
`context.assert.snapshot()`, which stores snapshots in external `.snapshot`
files, inline snapshots store the expected value directly in the test file as
template literals.

When run with [`--test-update-snapshots`][], the test runner will automatically
modify the source code to insert or update the `expected` template literal.

```js
test('inline snapshot test', (t) => {
const user = { name: 'Alice', age: 30 };

// First run: call without expected value
t.assert.inlineSnapshot(user);

// After running with --test-update-snapshots, becomes:
// t.assert.inlineSnapshot(user, `
// {
// "name": "Alice",
// "age": 30
// }
// `);
});

test('inline snapshot with explicit expected value', (t) => {
t.assert.inlineSnapshot('hello', `
hello
`);
});
```

**Note:** For current limitations and restrictions of inline snapshots, see
[Inline snapshot limitations](#inline-snapshot-limitations).

### `context.diagnostic(message)`

<!-- YAML
Expand Down
Loading