vitest-dev/vitest · error · Error

Runner must export a default function, but got ${typeof mod.

Error message

Runner must export a default function, but got ${typeof mod.default} imported from ${config.runner}

What it means

Thrown by getTestRunnerConstructor when a custom runner is configured (`config.runner` points to a module path) but the imported module has no `default` export that is usable as a constructor. Vitest needs `export default class MyRunner ...` (or a function) so it can do `new TestRunner(config)`. The message reports the actual typeof value it received so you can see what shape the module exported.

Source

Thrown at packages/vitest/src/runtime/runners/index.ts:20

import type { SerializedConfig } from '../config'
import type { TestModuleRunner } from '../moduleRunner/testModuleRunner'
import type { VitestRunner, VitestRunnerConstructor } from '../runner/types'
import { takeCoverageInsideWorker } from '../../integrations/coverage'
import { rpc } from '../rpc'
import { loadDiffConfig, loadSnapshotSerializers } from '../setup-common'
import { getWorkerState } from '../utils'
import { TestRunner } from './test'

async function getTestRunnerConstructor(
  config: SerializedConfig,
  moduleRunner: TestModuleRunner,
): Promise<VitestRunnerConstructor> {
  if (!config.runner) {
    return TestRunner as any as VitestRunnerConstructor
  }
  const mod = await moduleRunner.import(config.runner)
  if (!mod.default && typeof mod.default !== 'function') {
    throw new Error(
      `Runner must export a default function, but got ${typeof mod.default} imported from ${
        config.runner
      }`,
    )
  }
  return mod.default as VitestRunnerConstructor
}

export async function resolveTestRunner(
  config: SerializedConfig,
  moduleRunner: TestModuleRunner,
  traces: Traces,
): Promise<VitestRunner> {
  const TestRunner = await getTestRunnerConstructor(config, moduleRunner)
  const testRunner = new TestRunner(config)

  // inject private executor to every runner
  Object.defineProperty(testRunner, 'moduleRunner', {

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Add `export default` to the runner module and make it a class or function: `export default class MyRunner extends VitestRunner { ... }`.
  2. Verify the path in `config.runner` resolves to the intended file.
  3. If using TypeScript, make sure the build emits a usable default export.
  4. Extend the VitestRunner base type from the runtime types so the shape is correct.

Example fix

// before (./my-runner.js)
export class MyRunner { /* ... */ }

// after
import { VitestRunner } from 'vitest/runners'
export default class MyRunner extends VitestRunner {
  importFile(file) { /* ... */ }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Verify your runner module before pointing config at it
import * as runnerMod from './my-runner.js'
if (typeof runnerMod.default !== 'function') {
  throw new Error('runner module must export a default class/function')
}

Type guard

import type { VitestRunnerConstructor } from 'vitest'
function isRunnerCtor(mod: any): mod is { default: VitestRunnerConstructor } {
  return mod && typeof mod.default === 'function'
}

Prevention

When it happens

Trigger: Setting `test.runner: './my-runner.js'` where that file only has named exports, no default export. Exporting default as an object or a plain value instead of a class/function. Forgetting to add `export default` to the runner file.

Common situations: Writing a custom VitestRunner for the first time and omitting the default export. Refactoring a runner from default to named export. Pointing config.runner at the wrong file that happens to export something else.

Related errors


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