vitest-dev/vitest · error · TypeError

`bench()` expects a benchmark function. Call `bench(name, fn

Error message

`bench()` expects a benchmark function. Call `bench(name, fn)` or `bench(name, options, fn)`.

What it means

When the second argument to `bench()` is a non-function (treated as `options`), the third argument MUST be the benchmark function. `normalizeBenchArgs` (see `benchmark.ts:579`) throws this `TypeError` when slot 3 is missing or not a function — i.e. `bench(name, options)` with no `fn`, or with a non-function third arg.

Source

Thrown at packages/vitest/src/runtime/benchmark.ts:579

function formatModuleId(moduleId: string, root: string): string {
  if (!root || !isAbsolute(moduleId)) {
    return moduleId
  }
  return relative(root, moduleId)
}

function normalizeBenchArgs(
  a: BenchFn | BenchFnOptions,
  b: BenchFn | BenchFnOptions | undefined,
): { fn: BenchFn; fnOpts: BenchOptions | undefined; writeResult: string | undefined; perProject: boolean } {
  if (typeof a === 'function') {
    if (b !== undefined) {
      throw new TypeError('`bench()` does not accept options as the third argument. Pass options as the second argument instead: `bench(name, options, fn)`.')
    }
    return { fn: a, fnOpts: undefined, writeResult: undefined, perProject: false }
  }
  if (typeof b !== 'function') {
    throw new TypeError('`bench()` expects a benchmark function. Call `bench(name, fn)` or `bench(name, options, fn)`.')
  }
  // Strip vitest-specific fields only when present so we don't allocate a new
  // object — preserving referential identity matters: users inspect
  // `registration.fnOpts` and the provider sees the same object the caller
  // passed in.
  if (a.writeResult === undefined && a.perProject === undefined) {
    return { fn: b, fnOpts: a as BenchOptions, writeResult: undefined, perProject: false }
  }
  const { writeResult, perProject, ...fnOpts } = a
  return {
    fn: b,
    fnOpts: Object.keys(fnOpts).length > 0 ? fnOpts as BenchOptions : undefined,
    writeResult,
    perProject: perProject ?? false,
  }
}

function validateBenchmarkProject(config: SerializedConfig) {

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Add the function as the third argument: `bench('x', { iterations: 10 }, () => thing())`.
  2. If you have no options, use the two-arg form: `bench('x', () => thing())`.
  3. Double-check that slot 2 is options (object) and slot 3 is a function.

Example fix

// before
bench('sort', { iterations: 100 })

// after
bench('sort', { iterations: 100 }, () => sortArr())
Defensive patterns

Strategy: type-guard

Validate before calling

function benchWithOptions(name: string, options: BenchOptions, fn: unknown) {
  if (typeof fn !== 'function') {
    throw new TypeError('Third argument to bench(name, options, fn) must be a function')
  }
  return bench(name, options, fn)
}

Type guard

function isBenchFn(v: unknown): v is (...args: any[]) => any {
  return typeof v === 'function'
}

Try / catch

try {
  bench(name, options, fn)
} catch (e) {
  if (e instanceof TypeError && /expects a benchmark function/.test(e.message)) {
    // supply the missing function argument
  } else throw e
}

Prevention

When it happens

Trigger: `bench('x', { iterations: 10 })` (no fn); `bench('x', opts, 'notafn')`; `bench('x', opts, anotherOptionsObject)`.

Common situations: Forgetting the function when adding options; passing options in both slots; refactor that dropped the fn.

Related errors


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