Skip to content

Commit e12a5a3

Browse files
authoredJan 4, 2024
fix(vitest): throw an error if mock was already loaded when vi.mock is called (#4862)
1 parent 8780fe3 commit e12a5a3

File tree

4 files changed

+29
-7
lines changed

4 files changed

+29
-7
lines changed
 

‎docs/guide/common-errors.md

+16
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,19 @@ export default defineConfig({
4242
}
4343
})
4444
```
45+
46+
## Cannot mock "./mocked-file.js" because it is already loaded
47+
48+
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.
49+
50+
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.
51+
52+
```ts
53+
// setupFile.js
54+
import { vi } from 'vitest'
55+
import { sideEffect } from './mocked-file.js'
56+
57+
sideEffect()
58+
59+
vi.resetModules()
60+
```

‎packages/vitest/src/integrations/vi.ts

+2
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ function createVitest(): VitestUtils {
483483
path,
484484
importer,
485485
factory ? () => factory(() => _mocker.importActual(path, importer, _mocker.getMockContext().callstack)) : undefined,
486+
true,
486487
)
487488
},
488489

@@ -496,6 +497,7 @@ function createVitest(): VitestUtils {
496497
path,
497498
importer,
498499
factory ? () => factory(() => _mocker.importActual(path, importer, _mocker.getMockContext().callstack)) : undefined,
500+
false,
499501
)
500502
},
501503

‎packages/vitest/src/runtime/mocker.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export class VitestMocker {
166166
if (mock.type === 'unmock')
167167
this.unmockPath(fsPath)
168168
if (mock.type === 'mock')
169-
this.mockPath(mock.id, fsPath, external, mock.factory)
169+
this.mockPath(mock.id, fsPath, external, mock.factory, mock.throwIfCached)
170170
}))
171171

172172
VitestMocker.pendingIds = []
@@ -407,10 +407,13 @@ export class VitestMocker {
407407
this.deleteCachedItem(id)
408408
}
409409

410-
public mockPath(originalId: string, path: string, external: string | null, factory?: MockFactory) {
411-
const suitefile = this.getSuiteFilepath()
410+
public mockPath(originalId: string, path: string, external: string | null, factory: MockFactory | undefined, throwIfExists: boolean) {
412411
const id = this.normalizePath(path)
413412

413+
if (throwIfExists && this.moduleCache.has(id))
414+
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`)
415+
416+
const suitefile = this.getSuiteFilepath()
414417
const mocks = this.mockMap.get(suitefile) || {}
415418
const resolves = this.resolveCache.get(suitefile) || {}
416419

@@ -484,11 +487,11 @@ export class VitestMocker {
484487
return mock
485488
}
486489

487-
public queueMock(id: string, importer: string, factory?: MockFactory) {
488-
VitestMocker.pendingIds.push({ type: 'mock', id, importer, factory })
490+
public queueMock(id: string, importer: string, factory?: MockFactory, throwIfCached = false) {
491+
VitestMocker.pendingIds.push({ type: 'mock', id, importer, factory, throwIfCached })
489492
}
490493

491-
public queueUnmock(id: string, importer: string) {
492-
VitestMocker.pendingIds.push({ type: 'unmock', id, importer })
494+
public queueUnmock(id: string, importer: string, throwIfCached = false) {
495+
VitestMocker.pendingIds.push({ type: 'unmock', id, importer, throwIfCached })
493496
}
494497
}

‎packages/vitest/src/types/mocker.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ export interface PendingSuiteMock {
77
id: string
88
importer: string
99
type: 'mock' | 'unmock'
10+
throwIfCached: boolean
1011
factory?: MockFactory
1112
}

0 commit comments

Comments
 (0)
Please sign in to comment.