vitest-dev/vitest · error · Error

done() callback is deprecated, use promise instead

Error message

done() callback is deprecated, use promise instead

What it means

The test context object is itself a callable function in Vitest. Vitest intentionally throws this error when the context is invoked directly, because the old Node.js/Tapas-style `done()` callback pattern (where you call `done()` to signal test completion) is deprecated. Vitest expects tests to return a Promise or be async instead of calling a completion callback. The error is thrown at context construction time in `createTestContext` (context.ts:162-164).

Source

Thrown at packages/vitest/src/runtime/runner/context.ts:163

const abortControllers = new WeakMap<TestContext, AbortController>()

export function abortIfTimeout([context]: [TestContext?, unknown?], error: Error): void {
  if (context) {
    abortContextSignal(context, error)
  }
}

export function abortContextSignal(context: TestContext, error: Error): void {
  const abortController = abortControllers.get(context)
  abortController?.abort(error)
}

export function createTestContext(
  test: Test,
  runner: VitestRunner,
): TestContext {
  const context = function () {
    throw new Error('done() callback is deprecated, use promise instead')
  } as unknown as WriteableTestContext

  let abortController = abortControllers.get(context)

  if (!abortController) {
    abortController = new AbortController()
    abortControllers.set(context, abortController)
  }

  context.signal = abortController.signal
  context.task = test

  context.skip = (condition?: boolean | string, note?: string): never => {
    if (condition === false) {
      // do nothing
      return undefined as never
    }
    test.result ??= { state: 'skip' }

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Convert the test to async/await: `test('x', async () => { await doWork(); })` and remove all `done()` calls.
  2. If `done` is passed to a callback-based API, wrap it: `await new Promise(resolve => apiCall(resolve))` instead of calling `done`.
  3. Ensure no code treats the `TestContext` object as a callable function.

Example fix

// before
test('loads data', (done) => {
  fetchData((err, data) => {
    expect(data).toBeTruthy()
    done()
  })
})
// after
test('loads data', async () => {
  const data = await new Promise((resolve, reject) =>
    fetchData((err, d) => err ? reject(err) : resolve(d))
  )
  expect(data).toBeTruthy()
})
Defensive patterns

Strategy: validation

Validate before calling

// Ensure test functions are async and never invoke the context as a callable.
// Lint rule: disallow a parameter named 'done' in test callbacks.
// eslintrc example: { 'rules': { 'no-restricted-syntax': ['error', { 'selector': "CallExpression[callee.name='test'][arguments.0.type!='ArrowFunction']", 'message': 'Use async tests, not done callbacks' }] } }

Prevention

When it happens

Trigger: Writing a test as `test('x', (done) => { doWork(); done(); })` and then invoking the context argument as a function; calling the `TestContext` object directly, e.g. `ctx()` inside a fixture or custom matcher that received the context.

Common situations: Migrating from Mocha/Jasmine/Jest done-callback tests to Vitest; third-party libraries that call a context-like argument as a function; legacy test code using `done`-callback style.

Related errors


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