til / Vitest hoisted and destructuring
Vitest v0.31.0 introduced vi.hoisted
, which enables us to execute code before imports. This allows us to do things like adjusting the system time for imported code.
1vi.hoisted(() => vi.setSystemTime(new Date(1985, 11, 12)))
We couldn’t reference variables inside vi.mock
before, but now that the hoisted code is executed first, we can use it to pass variables to vi.mock
.
1const { mockedMethod } = vi.hoisted(() => {
2 return { mockedMethod: vi.fn() }
3})
4
5vi.mock('./path/to/module.js', () => {
6 return { originalMethod: mockedMethod }
7})
To make this all possible, Vitest needs to modify all imports to dynamic imports when we use vi.mock
. This happens behind the scenes.
1// Input code
2import { it, expect, vi } from 'vitest';
3import { value } from './my-module';
4
5vi.mock('./some-file');
6
7test('should be 1', () => {
8 expect(value).toBe(1);
9});
10
11// What Vitest converts it to
12const { it, expect, vi } = await import('vitest');
13
14vi.mock('./some-file');
15
16const { value } = await import('./my-module');
17
18test('should be 1', () => {
19 expect(value).toBe(1); // This won't work
20});
We are using destructuring to get the value
from my-module
, but this breaks the getter and we won’t get the right value. There is an open issue about this. As a workaround, we can write the dynamic import ourselves.
1import { it, expect, vi } from 'vitest';
2const myModule = await import('./my-module');
3
4vi.mock('./some-file');
5
6test('should be 1', () => {
7 expect(myModule.value).toBe(1);
8});