vitest-dev/vitest · error · TypeError

[vitest] Manual mocks require a factory function.

Error message

[vitest] Manual mocks require a factory function.

What it means

Manual mocks are resolved lazily by invoking a factory function that returns the module's exports. Registering a `'manual'` mock therefore requires the 5th argument to be a callable factory; anything else (omitted, a string, an object) is invalid because the mock could never produce exports.

Source

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

        throw new Error(`Unknown mock type: ${(event as any).type}`)
      }
    }

    if (typeof raw !== 'string') {
      throw new TypeError('[vitest] Mocks require a raw string.')
    }

    if (typeof url !== 'string') {
      throw new TypeError('[vitest] Mocks require a url string.')
    }

    if (typeof id !== 'string') {
      throw new TypeError('[vitest] Mocks require an id string.')
    }

    if (type === 'manual') {
      if (typeof factoryOrRedirect !== 'function') {
        throw new TypeError('[vitest] Manual mocks require a factory function.')
      }
      const mock = new ManualMockedModule(raw, id, url, factoryOrRedirect)
      this.add(mock)
      return mock
    }
    else if (type === 'automock' || type === 'autospy') {
      const mock = type === 'automock'
        ? new AutomockedModule(raw, id, url)
        : new AutospiedModule(raw, id, url)
      this.add(mock)
      return mock
    }
    else if (type === 'redirect') {
      if (typeof factoryOrRedirect !== 'string') {
        throw new TypeError('[vitest] Redirect mocks require a redirect string.')
      }
      const mock = new RedirectedModule(raw, id, url, factoryOrRedirect)
      this.add(mock)

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Provide a factory returning the module namespace: `registry.register('manual', raw, id, url, () => ({ default: stub }))`.
  2. If you already have the exports object, wrap it in a thunk: `() => exportsObject`.
  3. Double-check the overload — the 5th arg's type depends on `type` (function for manual, string for redirect).

Example fix

// before
registry.register('manual', raw, id, url)

// after
registry.register('manual', raw, id, url, () => ({
  default: vi.fn(),
  helper: vi.fn(),
}))
Defensive patterns

Strategy: type-guard

Validate before calling

function registerManual(registry: any, raw: string, id: string, url: string, factory: unknown) {
  if (typeof factory !== 'function') {
    throw new TypeError('Manual mocks require a factory function () => module namespace.')
  }
  registry.register('manual', raw, id, url, factory as () => any)
}

Type guard

function isFactory(v: unknown): v is () => unknown {
  return typeof v === 'function'
}

Prevention

When it happens

Trigger: Calling `registry.register('manual', raw, id, url)` (factory omitted) or `registry.register('manual', raw, id, url, 'notAFunction')`.

Common situations: Forgetting the factory; copy-pasting a redirect call into a manual call; passing an already-built object instead of a thunk.

Related errors


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