{"record":{"id":"4b22db87c0a7cbb9","repo":"vitest-dev/vitest","slug":"cannot-import-specifier-the-test-context-was","errorCode":null,"errorMessage":"Cannot import \"${specifier}\": the test context was torn down.","messagePattern":"Cannot import \"(.+?)\": the test context was torn down\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/vm/utils.ts","lineNumber":128,"sourceCode":"// The active executor of this worker: vm pools run one test file (and so\n// one executor) at a time, which lets script-level dynamic import callbacks\n// be static functions instead of per-executor closures. Node registers the\n// callback for the lifetime of the compiled script, so a closure would both\n// pin the executor's test file world and make compiled scripts unshareable\n// between contexts.\ninterface ActiveVmExecutor {\n  importModuleDynamically: (specifier: string, referencer: VMModule) => Promise<VMModule>\n}\n\nlet activeVmExecutor: ActiveVmExecutor | undefined\n\nexport function setActiveVmExecutor(executor: ActiveVmExecutor | undefined): void {\n  activeVmExecutor = executor\n}\n\nexport async function activeImportModuleDynamically(specifier: string, referencer: VMModule): Promise<VMModule> {\n  if (!activeVmExecutor) {\n    throw new Error(`Cannot import \"${specifier}\": the test context was torn down.`)\n  }\n  return activeVmExecutor.importModuleDynamically(specifier, referencer)\n}\n\n// Node never collects a vm context in which multiple scripts installed\n// closures, and `vm.SourceTextModule`s are pinned by the realm's base object\n// list: the ContextifyContext/ModuleWrap wrappers keep the whole context\n// reachable even through forced full GCs, so a long-lived vm worker\n// accumulates every test file's world until it hits `vmMemoryLimit` and gets\n// recycled, destroying the worker's compile caches with it. Clearing what the\n// test file added to the global object (and the DOM) caps what a pinned\n// context retains. Pristine globals are kept so that work queued before the\n// teardown (jsdom events, worker-scoped fixture cleanups) can still run.\nconst captureKeysScript = new vm.Script(\n  `Object.getOwnPropertyNames(globalThis).concat(Object.getOwnPropertySymbols(globalThis))`,\n  { filename: 'virtual:vitest-capture-context-keys.js' },\n)\n","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/vm/utils.ts#L110-L146","documentation":"Vitest routes vm-pool dynamic import() through a module-level activeVmExecutor set by setActiveVmExecutor(). When a dynamic import fires after the executor has been cleared (set to undefined during context teardown), activeImportModuleDynamically() has no executor to delegate to and throws this error for the given specifier.","triggerScenarios":"A pending microtask, setTimeout callback, or unresolved promise inside a test triggers `await import('...')` after the current test file's vm context has already been disposed. The import is routed through activeImportModuleDynamically, which sees activeVmExecutor === undefined.","commonSituations":"Async cleanup that outlives the test (a setInterval, an unawaited promise, an event listener), or code under test that lazily imports modules after the file finishes. Also seen when a worker is reused and the previous file's lingering callbacks fire.","solutions":["Ensure all async work in the test is awaited before the test completes so no import() runs post-teardown.","Disable worker reuse for isolation: test.isolate=true (default) or test.maxWorkers low enough to recycle workers per file.","Use vi.useFakeTimers or properly clear timers/listeners in afterEach to stop deferred callbacks from firing later.","Switch to a non-vm pool (threads/forks) if the late import is unavoidable and the test does not depend on vm semantics."],"exampleFix":"// before: import fires after teardown\nit('lazy', () => {\n  setTimeout(() => import('./late'), 1000)\n})\n\n// after: await the import inside the test\nit('lazy', async () => {\n  await import('./late')\n})","handlingStrategy":"validation","validationCode":"import { setActiveVmExecutor } from './utils'\n// before a deferred import, confirm the executor is still active\nif (!getActiveVmExecutor()) {\n  // skip or queue the import; context is gone\n  return\n}","typeGuard":"function canImportDynamically() {\n  return typeof activeVmExecutor?.importModuleDynamically === 'function'\n}","tryCatchPattern":"try {\n  await import(specifier)\n} catch (e) {\n  if (/the test context was torn down/.test(e.message)) {\n    // swallow or reschedule; do not retry blindly\n    return null\n  }\n  throw e\n}","preventionTips":["Await all imports inside the test body; never fire import() from unawaited timers.","Clear timers, listeners, and intervals in afterEach.","Disable worker reuse if lingering callbacks are suspected."],"tags":["vm","lifecycle","dynamic-import","teardown","async"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}