vitest-dev/vitest · error · Error

Cannot provide "${key}" because it's not serializable.

Error message

Cannot provide "${key}" because it's not serializable.

What it means

Vitest's project.provide() injects values into the test context via inject(), but those values cross a process boundary (worker threads / child processes) and so must be structured-cloneable. The method (project.ts:124) runs structuredClone(value) as a guard and rethrows with this message when it fails. Non-cloneable values (functions, DOM nodes, class instances with non-cloneable internals, objects with circular refs) cannot be delivered to tests.

Source

Thrown at packages/vitest/src/node/project.ts:132

          this._fetcher,
          this.config,
        )
  }

  // "provide" is a property, not a method to keep the context when destructed in the global setup,
  // making it a method would be a breaking change, and can be done in Vitest 3 at minimum
  /**
   * Provide a value to the test context. This value will be available to all tests with `inject`.
   */
  provide = <T extends keyof ProvidedContext & string>(
    key: T,
    value: ProvidedContext[T],
  ): void => {
    try {
      structuredClone(value)
    }
    catch (err) {
      throw new Error(
        `Cannot provide "${key}" because it's not serializable.`,
        {
          cause: err,
        },
      )
    }
    // casting `any` because the default type is `never` since `ProvidedContext` is empty
    (this._provided as any)[key] = value
  }

  /**
   * Get the provided context. The project context is merged with the global context.
   */
  getProvidedContext(): ProvidedContext {
    if (this.isRootProject()) {
      return this._provided
    }
    // globalSetup can run even if core workspace is not part of the test run

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Provide only plain serializable data: strings, numbers, booleans, arrays, plain objects, Date, RegExp, Map, Set, ArrayBuffer, TypedArrays.
  2. If you must pass a non-serializable resource, provide a factory pattern: provide a config descriptor (URLs, options) and construct the resource inside setup() or the test instead.
  3. Run structuredClone(value) yourself in a try/catch before calling provide to surface the exact clone failure and the offending key.
  4. For functions, use vi.fn() only inside tests; never inject live spies across the boundary.

Example fix

// before
project.provide('db', openDatabaseConnection())

// after
project.provide('dbConfig', { url: 'postgres://...', pool: 10 })
// construct the connection inside setupFiles or the test
Defensive patterns

Strategy: validation

Validate before calling

function isSerializable(value) {
  try { structuredClone(value); return true } catch { return false }
}
// before provide:
if (!isSerializable(value)) throw new Error(`Refusing to provide non-serializable value for key '${key}'`)
project.provide(key, value)

Type guard

function isStructuredCloneable<T>(v: T): boolean {
  try { structuredClone(v); return true } catch { return false }
}

Try / catch

try {
  project.provide(key, value)
} catch (e) {
  if (e instanceof Error && /not serializable/.test(e.message)) {
    // fall back to a serializable descriptor instead of the live object
  } else throw e
}

Prevention

When it happens

Trigger: Calling project.provide('myKey', value) where value is a function, a class instance holding a non-cloneable field, a cyclic object, a WeakMap/WeakSet, or any value structuredClone rejects. Also triggered indirectly via config.provide in the test config object.

Common situations: Passing a database connection handle, an Express app instance, a logger with open sockets, a function/callback, or a reactive store wrapper (e.g. a Vue ref's internals) into provide(). Also hits when provide receives an object that secretly contains a circular reference.

Related errors


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