vitest-dev/vitest · error · TypeError

invalid snapshot serializer in ${file}. Must have a 'test' m

Error message

invalid snapshot serializer in ${file}. Must have a 'test' method along with either a 'serialize' or 'print' method.

What it means

Thrown by loadSnapshotSerializers (as a TypeError) when the serializer's default object exists but does not conform to the required interface: it must have a `test` function AND either a `serialize` or a `print` function. These mirror Jest's snapshot serializer contract used by @vitest/snapshot.

Source

Thrown at packages/vitest/src/runtime/setup-common.ts:85

): Promise<void> {
  const files = config.snapshotSerializers

  const snapshotSerializers = await Promise.all(
    files.map(async (file) => {
      const mo = await moduleRunner.import(file)
      if (!mo || typeof mo.default !== 'object' || mo.default === null) {
        throw new Error(
          `invalid snapshot serializer file ${file}. Must export a default object`,
        )
      }

      const config = mo.default
      if (
        typeof config.test !== 'function'
        || (typeof config.serialize !== 'function'
          && typeof config.print !== 'function')
      ) {
        throw new TypeError(
          `invalid snapshot serializer in ${file}. Must have a 'test' method along with either a 'serialize' or 'print' method.`,
        )
      }

      return config as SnapshotSerializer
    }),
  )

  snapshotSerializers.forEach(serializer => addSerializer(serializer))
}

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Ensure the default object has `test(val): boolean`.
  2. Add exactly one of `serialize(val, config, indent, depth, refs, printer)` or `print(val, printer)` with those exact names.
  3. Double-check method spelling: it is `serialize`/`print`, not `serialise`/`printValue`.
  4. Reference Jest's snapshot serializer docs - Vitest mirrors that API.

Example fix

// before
export default {
  serialize: (val) => `Foo(${val})`,
  // missing test
}

// after
export default {
  test: val => val?.constructor?.name === 'Foo',
  serialize: (val, config, indent, depth, refs, printer) => `Foo(${printer(val.value, config, indent, depth, refs)})`,
}
Defensive patterns

Strategy: type-guard

Validate before calling

import serializerDefault from './my-serializer.js'
const s = serializerDefault as any
if (typeof s.test !== 'function' || (typeof s.serialize !== 'function' && typeof s.print !== 'function')) {
  throw new Error('serializer must have test() and serialize()|print()')
}

Type guard

function isSerializer(v: unknown): v is { test: Function; serialize?: Function; print?: Function } {
  if (!v || typeof v !== 'object') return false
  const o = v as any
  return typeof o.test === 'function' && (typeof o.serialize === 'function' || typeof o.print === 'function')
}

Prevention

When it happens

Trigger: A serializer default object that has `serialize` but no `test`. One that has `test` but neither `serialize` nor `print`. A serializer that exports `{ print }` only (no test). Misnaming the methods (e.g. `serializeValue` instead of `serialize`).

Common situations: Hand-writing a serializer from memory and forgetting `test`. Copying a partial example. Renaming methods during a refactor. Confusing this with the expect.addSnapshotSerializer inline shape.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/9d9f4acbe6e666f6.json. Report an issue: GitHub.