denoland/deno · error · TypeError
Snapshot serializer must return a string
Error message
Snapshot serializer must return a string
What it means
assertSnapshot serializes the actual value with options.serializer (or the built-in serialize) and requires the result to be a string before comparing it to the stored snapshot (cli/js/40_test_snapshot.js). If a custom serializer returns a number, object, or undefined, this TypeError is thrown at assertion time. Note that JSON.stringify — a common serializer choice — returns undefined for top-level undefined, null is fine, but symbols/functions also stringify to undefined.
Solutions
- Make the serializer return a string: wrap the result with String(...) or JSON.stringify(...).
- Drop the custom serializer to use the built-in default, which always returns a string.
- After fixing the serializer, regenerate snapshots with `deno test --update` if the output format changed.
Example fix
// before
await t.assertSnapshot(state, { serializer: (s) => s.items.length });
// after
await t.assertSnapshot(state, { serializer: (s) => String(s.items.length) }); Defensive patterns
Strategy: type-guard
Validate before calling
// Wrap any custom serializer so it always returns a string:
function strSerializer(fn) {
return (value) => {
const out = fn(value);
return typeof out === "string" ? out : JSON.stringify(out);
};
}
await t.assertSnapshot(value, { serializer: strSerializer(mySerializer) }); Type guard
/** @param {unknown} s @returns {s is string} */
const isString = (s) => typeof s === "string"; Prevention
- End every custom serializer with an explicit String(...) or JSON.stringify(...) return.
- Beware JSON.stringify returning undefined for top-level undefined/symbol/function values.
- If the serializer's output format changes, regenerate snapshots with `deno test --update`.
When it happens
Trigger: `t.assertSnapshot(v, { serializer: (x) => x.count })` (returns a number), `{ serializer: JSON.stringify }` when the value is undefined, or any serializer that forgets to stringify its result.
Common situations: Writing a compact custom serializer that extracts a field and forgets to convert it to a string; using JSON.stringify on values it cannot serialize at top level; changing a serializer's return type during a refactor.
Related errors
- Expected the second argument to assertSnapshot() to be an…
- Missing snapshot file.
- Missing snapshot
- A key and certificate are required for `Deno.listenTls`
- If `cert` is specified, `key` must be specified as well for
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/cf0f6909ccaee95f.
Report an issue: GitHub.
Appendix: source
Thrown at cli/js/40_test_snapshot.js:337
throw new TypeError(
"Expected the second argument to assertSnapshot() to be an options object or a message string",
);
}
const context = getSnapshotContext(options);
const testName = options.name ?? getFullTestName(tContext);
const count = (MapPrototypeGet(context.counts, testName) ?? 0) + 1;
MapPrototypeSet(context.counts, testName, count);
const name = `${testName} ${count}`;
if (!ArrayPrototypeIncludes(context.updateQueue, name)) {
ArrayPrototypePush(context.updateQueue, name);
}
const serializer = options.serializer ?? serialize;
const actualSnapshot = serializer(actual);
if (typeof actualSnapshot !== "string") {
throw new TypeError("Snapshot serializer must return a string");
}
const expectedSnapshot = MapPrototypeGet(context.currentValues, name);
if (getIsUpdateMode()) {
if (actualSnapshot !== expectedSnapshot) {
MapPrototypeSet(context.updatedValues, name, actualSnapshot);
if (!ArrayPrototypeIncludes(context.updatedNames, name)) {
ArrayPrototypePush(context.updatedNames, name);
}
}
return;
}
if (!context.fileExists) {
throw new AssertionError(
getErrorMessage("Missing snapshot file.", options),
);View on GitHub (pinned to 9ad36f7a2c)