{"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":412,"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":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/spy/src/index.ts#L394-L430","documentation":"Thrown by vi.spyOn() (packages/spy/src/index.ts) when reassigning the spied property fails with a TypeError on an object whose [Symbol.toStringTag] is 'Module'. ES module namespace objects are frozen and non-configurable, so you cannot redefine an export in place. Vitest catches the underlying 'Cannot redefine property'/'Cannot replace module namespace' error and rethrows with a pointer to the mocking guide, because the correct approach is vi.mock() at the module level.","triggerScenarios":"import * as mod from './mod'; vi.spyOn(mod, 'namedExport'); importing an ESM module as a namespace and trying to spy on one of its exports at runtime; spying on a CJS interop wrapper that presents as a Module namespace.","commonSituations":"Migrating from CommonJS to ESM where vi.spyOn used to work; trying to override a named export of a third-party ESM dependency; running tests with the ESM loader; spying before calling vi.mock() in a hoisted block.","solutions":["Use vi.mock('./mod', ...) at the top of the test file (hoisted) to replace the module's exports.","Inside vi.mock's factory, return an object with the export replaced by vi.fn().","If the export is a function used internally, refactor the source to import it from a dependency module you can mock.","Avoid import * as for ESM and prefer named imports combined with vi.mock."],"exampleFix":"// before\nimport * as utils from './utils'\nvi.spyOn(utils, 'greet')\n\n// after\nvi.mock('./utils', async (importOriginal) => {\n  const actual = await importOriginal()\n  return { ...actual, greet: vi.fn() }\n})","handlingStrategy":"validation","validationCode":"function isModuleNamespace(obj: unknown): boolean {\n  return typeof obj === 'object' && obj !== null\n    && (obj as any)[Symbol.toStringTag] === 'Module'\n}\nif (isModuleNamespace(mod)) {\n  // use vi.mock instead of vi.spyOn\n}","typeGuard":"const isModuleNamespace = (obj: unknown): boolean =>\n  typeof obj === 'object' && obj !== null && (obj as any)[Symbol.toStringTag] === 'Module'","tryCatchPattern":null,"preventionTips":["Prefer vi.mock() with a factory for replacing ESM named exports.","Avoid import * as for modules you intend to spy on at runtime.","Refactor source to consume injectable dependencies rather than reaching into another module's namespace."],"tags":["spy","mocking","esm","module-namespace"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}