diff --git a/docs/guide/common-errors.md b/docs/guide/common-errors.md index 50bc325f08dc..2dc78952c326 100644 --- a/docs/guide/common-errors.md +++ b/docs/guide/common-errors.md @@ -42,3 +42,19 @@ export default defineConfig({ } }) ``` + +## Cannot mock "./mocked-file.js" because it is already loaded + +This error happens when `vi.mock` method is called on a module that was already loaded. Vitest throws this error because this call has no effect since cached modules are preferred. + +Remember that `vi.mock` is always hoisted - it means that the module was loaded before the test file started executing - most likely in a setup file. To fix the error, remove the import or clear the cache at the end of a setup file - beware that setup file and your test file will reference different modules in that case. + +```ts +// setupFile.js +import { vi } from 'vitest' +import { sideEffect } from './mocked-file.js' + +sideEffect() + +vi.resetModules() +``` diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 539eab323ac4..7d6bfcb37dd0 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -483,6 +483,7 @@ function createVitest(): VitestUtils { path, importer, factory ? () => factory(() => _mocker.importActual(path, importer, _mocker.getMockContext().callstack)) : undefined, + true, ) }, @@ -496,6 +497,7 @@ function createVitest(): VitestUtils { path, importer, factory ? () => factory(() => _mocker.importActual(path, importer, _mocker.getMockContext().callstack)) : undefined, + false, ) }, diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index 320f26f24405..f07182ded7df 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -166,7 +166,7 @@ export class VitestMocker { if (mock.type === 'unmock') this.unmockPath(fsPath) if (mock.type === 'mock') - this.mockPath(mock.id, fsPath, external, mock.factory) + this.mockPath(mock.id, fsPath, external, mock.factory, mock.throwIfCached) })) VitestMocker.pendingIds = [] @@ -407,10 +407,13 @@ export class VitestMocker { this.deleteCachedItem(id) } - public mockPath(originalId: string, path: string, external: string | null, factory?: MockFactory) { - const suitefile = this.getSuiteFilepath() + public mockPath(originalId: string, path: string, external: string | null, factory: MockFactory | undefined, throwIfExists: boolean) { const id = this.normalizePath(path) + if (throwIfExists && this.moduleCache.has(id)) + throw new Error(`[vitest] Cannot mock "${originalId}" because it is already loaded. Did you import it in a setup file?\n\nPlease, remove the import if you want static imports to be mocked, or clear module cache by calling "vi.resetModules()" before mocking if you are going to import the file again. See: https://vitest.dev/guide/common-errors.html#cannot-mock-mocked-file.js-because-it-is-already-loaded`) + + const suitefile = this.getSuiteFilepath() const mocks = this.mockMap.get(suitefile) || {} const resolves = this.resolveCache.get(suitefile) || {} @@ -484,11 +487,11 @@ export class VitestMocker { return mock } - public queueMock(id: string, importer: string, factory?: MockFactory) { - VitestMocker.pendingIds.push({ type: 'mock', id, importer, factory }) + public queueMock(id: string, importer: string, factory?: MockFactory, throwIfCached = false) { + VitestMocker.pendingIds.push({ type: 'mock', id, importer, factory, throwIfCached }) } - public queueUnmock(id: string, importer: string) { - VitestMocker.pendingIds.push({ type: 'unmock', id, importer }) + public queueUnmock(id: string, importer: string, throwIfCached = false) { + VitestMocker.pendingIds.push({ type: 'unmock', id, importer, throwIfCached }) } } diff --git a/packages/vitest/src/types/mocker.ts b/packages/vitest/src/types/mocker.ts index e39a880eb570..76f759de3318 100644 --- a/packages/vitest/src/types/mocker.ts +++ b/packages/vitest/src/types/mocker.ts @@ -7,5 +7,6 @@ export interface PendingSuiteMock { id: string importer: string type: 'mock' | 'unmock' + throwIfCached: boolean factory?: MockFactory }