Skip to content

Commit a48be6f

Browse files
authored
fix(vitest): correctly resolve mocked node:* imports in __mocks__ folder (#6204)
1 parent 3aab8a1 commit a48be6f

File tree

9 files changed

+109
-16
lines changed

9 files changed

+109
-16
lines changed

docs/guide/mocking.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -383,19 +383,19 @@ module.exports = fs.promises
383383
:::
384384

385385
```ts
386-
// hello-world.js
386+
// read-hello-world.js
387387
import { readFileSync } from 'node:fs'
388388

389389
export function readHelloWorld(path) {
390-
return readFileSync('./hello-world.txt')
390+
return readFileSync(path)
391391
}
392392
```
393393

394394
```ts
395395
// hello-world.test.js
396396
import { beforeEach, expect, it, vi } from 'vitest'
397397
import { fs, vol } from 'memfs'
398-
import { readHelloWorld } from './hello-world.js'
398+
import { readHelloWorld } from './read-hello-world.js'
399399

400400
// tell vitest to use fs mock from __mocks__ folder
401401
// this can be done in a setup file if fs should always be mocked
@@ -408,7 +408,7 @@ beforeEach(() => {
408408
})
409409

410410
it('should return correct text', () => {
411-
const path = './hello-world.txt'
411+
const path = '/hello-world.txt'
412412
fs.writeFileSync(path, 'hello world')
413413

414414
const text = readHelloWorld(path)
@@ -423,7 +423,7 @@ it('can return a value multiple times', () => {
423423
'./dir2/hw.txt': 'hello dir2',
424424
},
425425
// default cwd
426-
'/tmp'
426+
'/tmp',
427427
)
428428

429429
expect(readHelloWorld('/tmp/dir1/hw.txt')).toBe('hello dir1')

packages/vitest/src/runtime/mocker.ts

+22-8
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export class VitestMocker {
188188
return {
189189
id,
190190
fsPath,
191-
external,
191+
external: external ? this.normalizePath(external) : external,
192192
}
193193
}
194194

@@ -315,14 +315,28 @@ export class VitestMocker {
315315
const files = readdirSync(mockFolder)
316316
const baseOriginal = basename(path)
317317

318-
for (const file of files) {
319-
const baseFile = basename(file, extname(file))
320-
if (baseFile === baseOriginal) {
321-
return resolve(mockFolder, file)
318+
function findFile(files: string[], baseOriginal: string): string | null {
319+
for (const file of files) {
320+
const baseFile = basename(file, extname(file))
321+
if (baseFile === baseOriginal) {
322+
const path = resolve(mockFolder, file)
323+
// if the same name, return the file
324+
if (fs.statSync(path).isFile()) {
325+
return path
326+
}
327+
else {
328+
// find folder/index.{js,ts}
329+
const indexFile = findFile(readdirSync(path), 'index')
330+
if (indexFile) {
331+
return indexFile
332+
}
333+
}
334+
}
322335
}
336+
return null
323337
}
324338

325-
return null
339+
return findFile(files, baseOriginal)
326340
}
327341

328342
const dir = dirname(path)
@@ -517,7 +531,7 @@ export class VitestMocker {
517531
const mocks = this.mockMap.get(suitefile) || {}
518532
const resolves = this.resolveCache.get(suitefile) || {}
519533

520-
mocks[id] = factory || this.resolveMockPath(path, external)
534+
mocks[id] = factory || this.resolveMockPath(id, external)
521535
resolves[id] = originalId
522536

523537
this.mockMap.set(suitefile, mocks)
@@ -546,7 +560,7 @@ export class VitestMocker {
546560
let mock = this.getDependencyMock(normalizedId)
547561

548562
if (mock === undefined) {
549-
mock = this.resolveMockPath(fsPath, external)
563+
mock = this.resolveMockPath(normalizedId, external)
550564
}
551565

552566
if (mock === null) {

pnpm-lock.yaml

+21-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/core/__mocks__/fs.cjs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// we can also use `import`, but then
2+
// every export should be explicitly defined
3+
4+
const { fs } = require('memfs')
5+
6+
module.exports = fs

test/core/__mocks__/fs/promises.cjs

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// we can also use `import`, but then
2+
// every export should be explicitly defined
3+
4+
const { fs } = require('memfs')
5+
6+
module.exports = fs.promises

test/core/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"axios": "^0.26.1",
2424
"debug": "^4.3.4",
2525
"immutable": "5.0.0-beta.5",
26+
"memfs": "^4.8.2",
2627
"strip-ansi": "^7.1.0",
2728
"sweetalert2": "^11.6.16",
2829
"tinyrainbow": "^1.2.0",

test/core/src/read-hello-world.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// hello-world.js
2+
import { readFileSync } from 'node:fs'
3+
4+
export function readHelloWorld(path: string) {
5+
return readFileSync(path, 'utf-8')
6+
}

test/core/test/file-path.test.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { existsSync } from 'node:fs'
22
import { describe, expect, it, vi } from 'vitest'
33
import { isWindows, slash, toFilePath } from '../../../packages/vite-node/src/utils'
44

5-
vi.mock('fs')
5+
vi.mock('fs', () => {
6+
return {
7+
existsSync: vi.fn(),
8+
}
9+
})
610

711
describe('current url', () => {
812
it('__filename is equal to import.meta.url', () => {

test/core/test/mock-fs.test.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// hello-world.test.js
2+
import { beforeEach, expect, it, vi } from 'vitest'
3+
import { fs, vol } from 'memfs'
4+
import { readHelloWorld } from '../src/read-hello-world'
5+
6+
// tell vitest to use fs mock from __mocks__ folder
7+
// this can be done in a setup file if fs should always be mocked
8+
vi.mock('node:fs')
9+
vi.mock('node:fs/promises')
10+
11+
beforeEach(() => {
12+
// reset the state of in-memory fs
13+
vol.reset()
14+
})
15+
16+
it('should return correct text', () => {
17+
const path = '/hello-world.txt'
18+
fs.writeFileSync(path, 'hello world')
19+
20+
const text = readHelloWorld(path)
21+
expect(text).toBe('hello world')
22+
})
23+
24+
it('can return a value multiple times', () => {
25+
// you can use vol.fromJSON to define several files
26+
vol.fromJSON(
27+
{
28+
'./dir1/hw.txt': 'hello dir1',
29+
'./dir2/hw.txt': 'hello dir2',
30+
},
31+
// default cwd
32+
'/tmp',
33+
)
34+
35+
expect(readHelloWorld('/tmp/dir1/hw.txt')).toBe('hello dir1')
36+
expect(readHelloWorld('/tmp/dir2/hw.txt')).toBe('hello dir2')
37+
})

0 commit comments

Comments
 (0)