{"record":{"id":"e714ca69d975a0d6","repo":"vitest-dev/vitest","slug":"cannot-spy-on-export-string-key-module-name","errorCode":null,"errorMessage":"Cannot spy on export \"${String(key)}\". Module namespace is not configurable in ESM. See: https://vitest.dev/guide/mocking/modules#mocking-a-module","messagePattern":"Cannot spy on export \"(.+?)\"\\. Module namespace is not configurable in ESM\\. See: https://vitest\\.dev/guide/mocking/modules#mocking-a-module","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/spy/src/index.ts","lineNumber":436,"sourceCode":"  })\n\n  try {\n    reassign(\n      ssr\n        ? () => mock\n        : mock,\n    )\n  }\n  catch (error) {\n    if (\n      error instanceof TypeError\n      && Symbol.toStringTag\n      && (object as any)[Symbol.toStringTag] === 'Module'\n      && (error.message.includes('Cannot redefine property')\n        || error.message.includes('Cannot replace module namespace')\n        || error.message.includes('can\\'t redefine non-configurable property'))\n    ) {\n      throw new TypeError(\n        `Cannot spy on export \"${String(key)}\". Module namespace is not configurable in ESM. See: https://vitest.dev/guide/mocking/modules#mocking-a-module`,\n        { cause: error },\n      )\n    }\n\n    throw error\n  }\n\n  return mock\n}\n\nfunction getDescriptor(obj: any, method: string | symbol | number): [any, PropertyDescriptor] | undefined {\n  const objDescriptor = Object.getOwnPropertyDescriptor(obj, method)\n  if (objDescriptor) {\n    return [obj, objDescriptor]\n  }\n  let currentProto = Object.getPrototypeOf(obj)\n  while (currentProto !== null) {","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/spy/src/index.ts#L418-L454","documentation":"vi.spyOn attempts to redefine a property on the target object. ESM module namespace objects have non-configurable exports by spec, so redefinition throws a TypeError that Vitest catches and re-wraps into this actionable message. The library cannot patch live ESM namespace bindings; mocking must go through vi.mock at the module level instead.","triggerScenarios":"Calling vi.spyOn on an imported ESM module's named export directly, e.g. `vi.spyOn(myModule, 'myFunc')` where myModule is a namespace object from a real ESM import (not a CJS interop).","commonSituations":"Migrating from Jest where spyOn on module exports worked via CJS; importing a pure-ESM dependency and trying to spy on its functions at runtime; testing code that wasn't designed with dependency injection.","solutions":["Use vi.mock('module-name', () => ({ myFunc: vi.fn() })) to replace the module before it is imported.","Refactor the code under test to accept the function as a parameter (dependency injection) so you can pass a spy directly.","If the module has a default export that is an object, spy on that object's method instead of the namespace binding."],"exampleFix":"// before — fails because ESM namespace is non-configurable:\nimport * as mod from './mod'\nvi.spyOn(mod, 'doThing')\n\n// after — mock at the module level:\nvi.mock('./mod', () => ({ doThing: vi.fn() }))\nimport { doThing } from './mod'","handlingStrategy":"type-guard","validationCode":"import { isModuleNamespaceObject } from 'util/types'\n\n// before spying, check if the target is an ESM namespace:\nif (isModuleNamespaceObject(target)) {\n  // use vi.mock instead of vi.spyOn\n}","typeGuard":"function isESMNamespace(obj: unknown): boolean {\n  return typeof obj === 'object' && obj !== null\n    && Object.prototype.toString.call(obj) === '[object Module]'\n}","tryCatchPattern":"try {\n  vi.spyOn(mod, 'fn')\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes('Module namespace is not configurable')) {\n    // fall back to vi.mock at module level\n    vi.mock('mod', () => ({ fn: vi.fn() }))\n  } else { throw e }\n}","preventionTips":["Prefer vi.mock for ESM modules over vi.spyOn on namespace exports.","Design code with dependency injection so functions can be spied without touching module bindings.","Check whether a module is CJS or ESM before attempting spyOn on its exports."],"tags":["spy","esm","mocking","module-namespace"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}