vitest-dev/vitest · error · TypeError

[vitest] Cannot register a mock that is already defined. Exp

Error message

[vitest] Cannot register a mock that is already defined. Expected a JSON representation from `MockedModule.toJSON`, instead got "${event.type}". Use "registry.add()" to update a mock instead.

What it means

`MockerRegistry.register()` is overloaded to accept either positional args (build a new mock) or a plain serialized JSON object (rehydrate a mock transferred between processes). Passing an already-constructed `MockedModule` class instance is rejected because the registry expects the DTO shape produced by `toJSON()`. Use `registry.add()` to insert a live instance directly.

Source

Thrown at packages/mocker/src/registry.ts:65

  ): AutospiedModule
  public register(
    typeOrEvent: MockedModuleType | MockedModuleSerialized,
    raw?: string,
    id?: string,
    url?: string,
    factoryOrRedirect?: string | (() => any),
  ): MockedModule {
    const type = typeof typeOrEvent === 'object' ? typeOrEvent.type : typeOrEvent

    if (typeof typeOrEvent === 'object') {
      const event = typeOrEvent
      if (
        event instanceof AutomockedModule
        || event instanceof AutospiedModule
        || event instanceof ManualMockedModule
        || event instanceof RedirectedModule
      ) {
        throw new TypeError(
          `[vitest] Cannot register a mock that is already defined. `
          + `Expected a JSON representation from \`MockedModule.toJSON\`, instead got "${event.type}". `
          + `Use "registry.add()" to update a mock instead.`,
        )
      }
      if (event.type === 'automock') {
        const module = AutomockedModule.fromJSON(event)
        this.add(module)
        return module
      }
      else if (event.type === 'autospy') {
        const module = AutospiedModule.fromJSON(event)
        this.add(module)
        return module
      }
      else if (event.type === 'redirect') {
        const module = RedirectedModule.fromJSON(event)
        this.add(module)

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Serialize the instance first: `registry.register(mock.toJSON())`.
  2. Or add the instance directly: `registry.add(mock)` (no validation/clone, just insert).
  3. If forwarding between processes, always call `.toJSON()` before IPC and reconstruct on the other side.

Example fix

// before
registry.register(existingMock)

// after
registry.register(existingMock.toJSON())
// or, for a live instance on the same process:
registry.add(existingMock)
Defensive patterns

Strategy: type-guard

Validate before calling

import {
  AutomockedModule, AutospiedModule,
  ManualMockedModule, RedirectedModule,
} from '@vitest/mocker/registry'

function isMockInstance(v: unknown) {
  return v instanceof AutomockedModule
    || v instanceof AutospiedModule
    || v instanceof ManualMockedModule
    || v instanceof RedirectedModule
}

// route live instances to add(), DTOs to register()
function upsert(registry: any, mock: unknown) {
  if (isMockInstance(mock)) registry.add(mock)
  else registry.register(mock)
}

Type guard

function isSerializedMockDTO(v: unknown): v is { type: string } {
  return typeof v === 'object' && v !== null
    && typeof (v as any).type === 'string'
    && !(v instanceof AutomockedModule
      || v instanceof AutospiedModule
      || v instanceof ManualMockedModule
      || v instanceof RedirectedModule)
}

Prevention

When it happens

Trigger: Calling `registry.register(existingModule)` where `existingModule` is an instance of `AutomockedModule`/`AutospiedModule`/`ManualMockedModule`/`RedirectedModule` rather than `existingModule.toJSON()`.

Common situations: Worker-thread mock synchronization forwarding live instances; internal tooling that wraps mocks; replaying a captured mock registry without serializing.

Related errors


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