denoland/deno · error · TypeError
Snapshot serializer must return a string
Error message
Snapshot serializer must return a string
What it means
Thrown by assertSnapshot() (cli/js/40_test_snapshot.js) when the serializer applied to the actual value does not return a string. The default serializer (serialize) always yields a string; a custom options.serializer must too, because snapshot files store plain string entries keyed by test name and counter.
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 89f33cbef2)
Solutions
- Make the serializer total: coerce the final result with String(...) or JSON.stringify(...)
- Handle every branch — no early returns that drop the value
- Unit-test the serializer directly against the same shapes your snapshots capture
Example fix
// before
await t.assertSnapshot(data, {
serializer: (v) => v.items.map((i) => i.name).join("\n") && v.items.length
? v.items.map((i) => i.name).join("\n")
: undefined,
});
// after
await t.assertSnapshot(data, {
serializer: (v) => v.items.map((i) => i.name).join("\n") ?? "",
}); Defensive patterns
Strategy: type-guard
Validate before calling
function makeSerializer(fn) {
return (value) => {
const out = fn(value);
if (typeof out !== "string") {
throw new TypeError(`serializer returned ${typeof out}; wrap with String()`);
}
return out;
};
}
// use: t.assertSnapshot(v, { serializer: makeSerializer(fmt) }) Type guard
/** @param {(v: unknown) => unknown} fn */
function returnsString(fn, sample) {
return typeof fn(sample) === "string";
} Prevention
- End every custom serializer with an explicit String(...) or JSON.stringify(...) coercion
- Unit-test serializers against the same value shapes the snapshots capture before running the suite
When it happens
Trigger: Passing { serializer: (v) => v } (identity on a non-string), a serializer returning undefined/null on some path (early return), or one returning an object/number like (v) => JSON.stringify(v, null, 2) missing the stringify. Fires per assertion, before any comparison.
Common situations: Writing a custom serializer that formats only the expected input shape and silently returns undefined for edge cases; reusing a formatter that returns numbers for numeric values; refactoring a serializer to return structured data instead of text.
Related errors
- Expected the second argument to assertSnapshot() to be an op
- Missing snapshot file.
- Missing snapshot: ${name}
- Snapshot does not match: [Diff] Actual / Expected ${di
- invalid doc test hashbang: #!/bin/sh (binary basename needs
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/cf0f6909ccaee95f.
Report an issue: GitHub.