Vi
Vitest는 개발을 돕기 위해 vi
헬퍼를 통해 유틸리티 함수를 제공합니다. 이 헬퍼는 전역적으로 접근할 수 있으며(globals 설정이 활성화된 경우), vitest
에서 직접 임포트할 수도 있습니다:
import { vi } from 'vitest';
모듈 모킹
이 섹션에서는 모듈 모킹 시 사용할 수 있는 API를 설명합니다. Vitest는 require()
를 사용하여 임포트된 모듈을 모킹하는 것을 지원하지 않으니 유의하십시오.
vi.mock
- 타입:
(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void
- 타입:
<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void
제공된 path
에서 임포트된 모든 모듈을 다른 모듈로 대체합니다. 경로 내에서 설정된 Vite 별칭을 사용할 수 있습니다. vi.mock
호출은 호이스팅되므로, 어디에서 호출하든 관계없이 항상 모든 임포트보다 먼저 실행됩니다. 스코프 외부 변수를 참조해야 하는 경우, vi.hoisted
내에서 정의한 후 vi.mock
내에서 참조할 수 있습니다.
WARNING
vi.mock
은 import
키워드로 임포트된 모듈에만 작동하며, require
에는 작동하지 않습니다.
vi.mock
을 호이스팅하기 위해 Vitest는 파일을 정적으로 분석합니다. 이는 vitest
패키지에서 직접 임포트되지 않은 vi
(예: 일부 유틸리티 파일에서 가져온 vi
)는 사용할 수 없음을 의미합니다. vitest
에서 임포트된 vi
와 함께 vi.mock
을 사용하거나 globals
설정 옵션을 활성화하십시오.
Vitest는 설정 파일 내에서 임포트된 모듈을 모킹하지 않습니다. 테스트 파일이 실행될 때 이미 캐시되어 있기 때문입니다. 테스트 파일을 실행하기 전에 모든 모듈 캐시를 지우려면 vi.hoisted
내에서 vi.resetModules()
를 호출할 수 있습니다.
factory
함수가 정의되면 모든 임포트는 그 결과를 반환합니다. Vitest는 팩토리 함수를 한 번만 호출하며, vi.unmock
또는 vi.doUnmock
이 호출될 때까지 모든 후속 임포트에 대해 그 결과를 캐시합니다.
jest
와는 다르게 팩토리 함수는 비동기적으로 동작할 수 있습니다. vi.importActual
또는 팩토리가 첫 번째 인수로 전달되는 헬퍼를 사용하여 원본 모듈에 접근할 수 있습니다.
팩토리 함수 대신 spy
속성을 포함하는 객체를 제공할 수도 있습니다. spy
가 true
이면 Vitest는 모듈을 평소와 같이 자동 모킹하지만, export의 구현을 재정의하지는 않습니다. 이는 export된 메서드가 다른 메서드에 의해 올바르게 호출되었는지 단순히 확인하려는 경우에 유용합니다.
import { calculator } from './src/calculator.ts';
vi.mock('./src/calculator.ts', { spy: true });
// 원본 구현을 호출하지만,
// 나중에 동작을 확인할 수 있습니다.
const result = calculator(1, 2);
expect(result).toBe(3);
expect(calculator).toHaveBeenCalledWith(1, 2);
expect(calculator).toHaveReturned(3);
Vitest는 또한 더 나은 IDE 지원을 위해 vi.mock
및 vi.doMock
메서드에서 문자열 대신 모듈 Promise를 지원합니다. 파일이 이동되면 경로가 업데이트되고 importOriginal
은 자동으로 타입을 추론합니다. 이 시그니처를 사용하면 팩토리 반환 타입이 원본 모듈과 호환되도록 강제됩니다(export는 선택 사항으로 유지).
// @filename: ./path/to/module.js
export declare function total(...numbers: number[]): number;
// @filename: test.js
import { vi } from 'vitest';
// ---cut---
vi.mock(import('./path/to/module.js'), async importOriginal => {
const mod = await importOriginal(); // 타입이 추론됩니다.
// ^?
return {
...mod,
// 일부 export를 대체합니다.
total: vi.fn(),
};
});
내부적으로 Vitest는 모듈 객체가 아닌 문자열을 기반으로 계속 작동합니다.
그러나 tsconfig.json
에 paths
별칭이 구성된 TypeScript를 사용하는 경우 컴파일러가 임포트 타입을 올바르게 해결할 수 없습니다. 이를 위해 모든 별칭 임포트를 해당 상대 경로로 바꾸십시오. 예: import('@/module')
대신 import('./path/to/module.js')
를 사용하십시오.
WARNING
vi.mock
은 파일의 맨 위로 호이스팅됩니다(즉, 이동됩니다). 이는 beforeEach
또는 test
내부에 작성하더라도 실제로는 그보다 먼저 호출된다는 의미입니다.
이는 또한 팩토리 외부에서 정의된 변수를 팩토리 내부에서 사용할 수 없음을 의미합니다.
팩토리 내부에서 변수를 사용해야 한다면 vi.doMock
을 사용해 보십시오. 이는 동일하게 작동하지만 호이스팅되지 않습니다. 다만, 후속 임포트만 모킹한다는 점에 유의하십시오.
vi.mock
이전에 선언된 경우 vi.hoisted
메서드로 정의된 변수를 참조할 수도 있습니다.
import { namedExport } from './path/to/module.js';
const mocks = vi.hoisted(() => {
return {
namedExport: vi.fn(),
};
});
vi.mock('./path/to/module.js', () => {
return {
namedExport: mocks.namedExport,
};
});
vi.mocked(namedExport).mockReturnValue(100);
expect(namedExport()).toBe(100);
expect(namedExport).toBe(mocks.namedExport);
WARNING
기본 export가 있는 모듈을 모킹하는 경우, 반환되는 팩토리 함수 객체 내에 default
키를 제공해야 합니다. 이는 ES 모듈의 특정 주의 사항입니다. 따라서 jest
는 CommonJS 모듈을 사용하므로 jest
문서는 다를 수 있습니다. 예를 들어,
vi.mock('./path/to/module.js', () => {
return {
default: { myDefaultKey: vi.fn() },
namedExport: vi.fn(),
// 기타...
};
});
모킹하려는 파일과 함께 __mocks__
폴더가 있고 팩토리가 제공되지 않은 경우, Vitest는 __mocks__
하위 폴더에서 동일한 이름의 파일을 찾아 실제 모듈로 사용하려고 시도합니다. 종속성을 모킹하는 경우, Vitest는 프로젝트의 루트(기본값: process.cwd()
)에서 __mocks__
폴더를 찾으려고 시도합니다. deps.moduleDirectories
설정 옵션을 통해 Vitest에 종속성 위치를 지정할 수 있습니다.
예를 들어, 다음과 같은 파일 구조가 있습니다.
- __mocks__
- axios.js
- src
__mocks__
- increment.js
- increment.js
- tests
- increment.test.js
테스트 파일에서 팩토리나 옵션 없이 vi.mock
을 호출하면 __mocks__
폴더에서 모듈로 사용할 파일을 찾습니다.
import { vi } from 'vitest';
// axios는 `__mocks__/axios.js`의 기본 export입니다.
import axios from 'axios';
// increment는 `src/__mocks__/increment.js`의 이름 있는 export입니다.
import { increment } from '../increment.js';
vi.mock('axios');
vi.mock('../increment.js');
axios.get(`/apples/${increment(1)}`);
WARNING
vi.mock
을 호출하지 않으면 모듈이 자동으로 모킹되지 않습니다. Jest의 자동 모킹 동작을 재현하려면 setupFiles
내에서 필요한 각 모듈에 대해 vi.mock
을 호출할 수 있습니다.
__mocks__
폴더가 없거나 팩토리 함수가 제공되지 않은 경우, Vitest는 원본 모듈을 임포트하고 모든 export를 자동 모킹합니다. 적용되는 규칙은 알고리즘을 참조하십시오.
vi.doMock
- 타입:
(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void
- 타입:
<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void
vi.mock
과 동일하지만 파일 맨 위로 호이스팅되지 않아 전역 파일 스코프에서 변수를 참조할 수 있습니다. 해당 모듈의 다음 동적 임포트는 모킹됩니다.
WARNING
이것은 호출되기 전에 임포트된 모듈을 모킹하지 않습니다. ESM의 모든 정적 임포트는 항상 호이스팅된다는 점을 명심하십시오. 따라서 정적 임포트 앞에 이를 두더라도 임포트 전에 호출되도록 강제하지 않습니다.
vi.doMock('./increment.js'); // 이것은 import 문 _이후_에 호출됩니다.
import { increment } from './increment.js';
export function increment(number) {
return number + 1;
}
import { beforeEach, test } from 'vitest';
import { increment } from './increment.js';
// vi.doMock이 아직 호출되지 않았으므로 모듈이 모킹되지 않습니다.
increment(1) === 2;
let mockedIncrement = 100;
beforeEach(() => {
// 팩토리 내부에서 변수에 접근할 수 있습니다.
vi.doMock('./increment.js', () => ({ increment: () => ++mockedIncrement }));
});
test('다음 모듈을 임포트하면 모킹된 모듈이 임포트됩니다.', async () => {
// vi.doMock이 임포트 _이후_에 평가되므로 원본 임포트는 모킹되지 않았습니다.
expect(increment(1)).toBe(2);
const { increment: mockedIncrement } = await import('./increment.js');
// 새로운 동적 임포트는 모킹된 모듈을 반환합니다.
expect(mockedIncrement(1)).toBe(101);
expect(mockedIncrement(1)).toBe(102);
expect(mockedIncrement(1)).toBe(103);
});
vi.mocked
- 타입:
<T>(obj: T, deep?: boolean) => MaybeMockedDeep<T>
- 타입:
<T>(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep<T>
TypeScript를 위한 타입 헬퍼입니다. 전달된 객체를 반환할 뿐입니다.
partial
이 true
이면 반환 값으로 Partial<T>
를 예상합니다. 기본적으로 TypeScript는 첫 번째 레벨 값만 모킹되었다고 인식합니다. 실제로 객체 전체가 모킹되었다고 TypeScript에 알리려면 두 번째 인수로 { deep: true }
를 전달할 수 있습니다.
export function add(x: number, y: number): number {
return x + y;
}
export function fetchSomething(): Promise<Response> {
return fetch('https://vitest.dev/');
}
import * as example from './example';
vi.mock('./example');
test('1 + 1은 10과 같습니다.', async () => {
vi.mocked(example.add).mockReturnValue(10);
expect(example.add(1, 1)).toBe(10);
});
test('부분적으로만 올바른 타입으로 반환 값을 모킹합니다.', async () => {
vi.mocked(example.fetchSomething).mockResolvedValue(new Response('hello'));
vi.mocked(example.fetchSomething, { partial: true }).mockResolvedValue({
ok: false,
});
// vi.mocked(example.someFn).mockResolvedValue({ ok: false }) // 이것은 타입 에러입니다.
});
vi.importActual
- 타입:
<T>(path: string) => Promise<T>
모듈이 모킹되어야 하는지 여부에 대한 모든 검사를 우회하여 모듈을 임포트합니다. 모듈을 부분적으로 모킹하려는 경우 유용하게 사용할 수 있습니다.
vi.mock('./example.js', async () => {
const originalModule = await vi.importActual('./example.js');
return { ...originalModule, get: vi.fn() };
});
vi.importMock
- 타입:
<T>(path: string) => Promise<MaybeMockedDeep<T>>
모든 속성(중첩 속성 포함)이 모킹된 모듈을 임포트합니다. vi.mock
과 동일한 규칙을 따릅니다. 적용되는 규칙은 알고리즘을 참조하십시오.
vi.unmock
- 타입:
(path: string | Promise<Module>) => void
모킹된 레지스트리에서 모듈을 제거합니다. 모든 임포트 호출은 이전에 모킹되었더라도 원본 모듈을 반환합니다. 이 호출은 파일 맨 위로 호이스팅되므로, 예를 들어 setupFiles
에 정의된 모듈만 언모킹합니다.
vi.doUnmock
- 타입:
(path: string | Promise<Module>) => void
vi.unmock
과 동일하지만 파일 맨 위로 호이스팅되지 않습니다. 해당 모듈의 다음 임포트는 모의 대신 원본 모듈을 임포트합니다. 이는 이전에 임포트된 모듈을 언모킹하지 않습니다.
export function increment(number) {
return number + 1;
}
import { increment } from './increment.js';
// vi.mock이 호이스팅되었으므로 increment는 이미 모킹되었습니다.
increment(1) === 100;
// 이것은 호이스팅되었고, 팩토리는 1행의 임포트 전에 호출됩니다.
vi.mock('./increment.js', () => ({ increment: () => 100 }));
// 모든 호출이 모킹되었고, `increment`는 항상 100을 반환합니다.
increment(1) === 100;
increment(30) === 100;
// 이것은 호이스팅되지 않으므로 다른 임포트는 언모킹된 모듈을 반환합니다.
vi.doUnmock('./increment.js');
// 이것은 여전히 100을 반환합니다. `vi.doUnmock`은 모듈을 재평가하지 않기 때문입니다.
increment(1) === 100;
increment(30) === 100;
// 다음 임포트는 언모킹되었고, 이제 `increment`는 count + 1을 반환하는 원본 함수입니다.
const { increment: unmockedIncrement } = await import('./increment.js');
unmockedIncrement(1) === 2;
unmockedIncrement(30) === 31;
vi.resetModules
- 타입:
() => Vitest
모든 모듈의 캐시를 지워 모듈 레지스트리를 재설정합니다. 이를 통해 재임포트될 때 모듈을 다시 평가할 수 있습니다. 최상위 임포트는 다시 평가될 수 없습니다. 로컬 상태가 테스트 간에 충돌하는 모듈을 격리하는 데 유용할 수 있습니다.
import { vi } from 'vitest';
import { data } from './data.js'; // 각 테스트 전에 재평가되지 않습니다.
beforeEach(() => {
vi.resetModules();
});
test('상태 변경', async () => {
const mod = await import('./some/path.js'); // 재평가됩니다.
mod.changeLocalState('new value');
expect(mod.getLocalState()).toBe('new value');
});
test('모듈이 이전 상태를 가집니다.', async () => {
const mod = await import('./some/path.js'); // 재평가됩니다.
expect(mod.getLocalState()).toBe('old value');
});
WARNING
모의 레지스트리를 재설정하지 않습니다. 모의 레지스트리를 지우려면 vi.unmock
또는 vi.doUnmock
을 사용하십시오.
vi.dynamicImportSettled
모든 임포트가 로드될 때까지 기다립니다. 동기 호출이 모듈 임포트를 시작하여 다른 방법으로는 기다릴 수 없는 경우에 유용합니다.
import { expect, test } from 'vitest';
// Promise가 반환되지 않으므로 임포트를 추적할 수 없습니다.
function renderComponent() {
import('./component.js').then(({ render }) => {
render();
});
}
test('작업이 해결됩니다.', async () => {
renderComponent();
await vi.dynamicImportSettled();
expect(document.querySelector('.component')).not.toBeNull();
});
TIP
동적 임포트 중에 다른 동적 임포트가 시작되면 이 메서드는 모든 임포트가 완료될 때까지 기다립니다.
이 메서드는 임포트가 해결된 후 다음 setTimeout
틱까지 기다리므로, 모든 동기 작업은 해결 시점까지 완료되어야 합니다.
함수 및 객체 모킹
이 섹션에서는 메서드 모킹과 환경 및 전역 변수 대체에 대해 설명합니다.
vi.fn
- 타입:
(fn?: Function) => Mock
함수에 대한 스파이를 생성하지만, 함수 없이도 초기화할 수 있습니다. 함수가 호출될 때마다 호출 인수, 반환 값, 그리고 인스턴스를 저장합니다. 또한 메서드를 사용하여 동작을 조작할 수 있습니다. 함수가 주어지지 않으면, 호출 시 모의는 undefined
를 반환합니다.
const getApples = vi.fn(() => 0);
getApples();
expect(getApples).toHaveBeenCalled();
expect(getApples).toHaveReturnedWith(0);
getApples.mockReturnValueOnce(5);
const res = getApples();
expect(res).toBe(5);
expect(getApples).toHaveNthReturnedWith(2, 5);
vi.mockObject 3.2.0+
- 타입:
<T>(value: T) => MaybeMockedDeep<T>
vi.mock()
이 모듈 export를 모킹하는 것과 동일한 방식으로, 주어진 객체의 속성과 메서드를 깊이 모킹합니다. 자세한 내용은 자동 모킹을 참조하십시오.
const original = {
simple: () => 'value',
nested: {
method: () => 'real',
},
prop: 'foo',
};
const mocked = vi.mockObject(original);
expect(mocked.simple()).toBe(undefined);
expect(mocked.nested.method()).toBe(undefined);
expect(mocked.prop).toBe('foo');
mocked.simple.mockReturnValue('mocked');
mocked.nested.method.mockReturnValue('mocked nested');
expect(mocked.simple()).toBe('mocked');
expect(mocked.nested.method()).toBe('mocked nested');
vi.isMockFunction
- 타입:
(fn: Function) => boolean
주어진 매개변수가 모의 함수인지 검사합니다. TypeScript를 사용하는 경우 타입도 추론하여 좁혀줍니다.
vi.clearAllMocks
모든 스파이에 대해 .mockClear()
를 호출합니다. 이는 모의 구현에 영향을 주지 않으면서 모의 기록을 지웁니다.
vi.resetAllMocks
모든 스파이에 대해 .mockReset()
을 호출합니다. 이는 모의 기록을 지우고 각 모의의 구현을 원본으로 재설정합니다.
vi.restoreAllMocks
모든 스파이에 대해 .mockRestore()
를 호출합니다. 이는 모의 기록을 지우고, 모든 원본 모의 구현을 복원하며, 스파이된 객체의 원본 디스크립터도 복원합니다.
vi.spyOn
- 타입:
<T, K extends keyof T>(object: T, method: K, accessType?: 'get' | 'set') => MockInstance
vi.fn()
과 유사하게 객체의 메서드 또는 getter/setter에 대한 스파이를 생성합니다. 이는 모의 함수를 반환합니다.
let apples = 0;
const cart = {
getApples: () => 42,
};
const spy = vi.spyOn(cart, 'getApples').mockImplementation(() => apples);
apples = 1;
expect(cart.getApples()).toBe(1);
expect(spy).toHaveBeenCalled();
expect(spy).toHaveReturnedWith(1);
TIP
명시적 리소스 관리를 지원하는 환경에서는 const
대신 using
을 사용하여 포함 블록이 종료될 때 모의 함수에 대해 mockRestore
를 자동으로 호출할 수 있습니다. 이는 스파이된 메서드에 특히 유용합니다.
it('console.log를 호출합니다.', () => {
using spy = vi.spyOn(console, 'log').mockImplementation(() => {})
debug('message')
expect(spy).toHaveBeenCalled()
})
// console.log가 여기서 복원됩니다.
TIP
afterEach
내에서 vi.restoreAllMocks
를 호출하거나 (test.restoreMocks
를 활성화하여) 모든 메서드를 원래 구현으로 복원할 수 있습니다. 이렇게 하면 원래 객체 디스크립터가 복원되므로, 메서드의 구현을 변경할 수 없습니다.
const cart = {
getApples: () => 42,
};
const spy = vi.spyOn(cart, 'getApples').mockReturnValue(10);
console.log(cart.getApples()); // 10
vi.restoreAllMocks();
console.log(cart.getApples()); // 42
spy.mockReturnValue(10);
console.log(cart.getApples()); // 여전히 42!
TIP
브라우저 모드에서는 export된 메서드를 스파이하는 것이 불가능합니다. 대신 vi.mock("./file-path.js", { spy: true })
를 호출하여 모든 export된 메서드를 스파이할 수 있습니다. 이렇게 하면 모든 export를 모킹하지만 구현은 그대로 유지됩니다. 따라서 메서드가 올바르게 호출되었는지 확인할 수 있습니다.
import { calculator } from './src/calculator.ts';
vi.mock('./src/calculator.ts', { spy: true });
calculator(1, 2);
expect(calculator).toHaveBeenCalledWith(1, 2);
expect(calculator).toHaveReturned(3);
jsdom
또는 다른 Node.js 환경에서 export를 스파이하는 것이 가능하지만, 이는 미래에 변경될 수 있습니다.
vi.stubEnv
- 타입:
<T extends string>(name: T, value: T extends "PROD" | "DEV" | "SSR" ? boolean : string | undefined) => Vitest
process.env
및 import.meta.env
의 환경 변수 값을 변경합니다. vi.unstubAllEnvs
를 호출하여 값을 복원할 수 있습니다.
import { vi } from 'vitest';
// `process.env.NODE_ENV` 및 `import.meta.env.NODE_ENV`는
// `vi.stubEnv` 호출 전에는 "development"입니다.
vi.stubEnv('NODE_ENV', 'production');
process.env.NODE_ENV === 'production';
import.meta.env.NODE_ENV === 'production';
vi.stubEnv('NODE_ENV', undefined);
process.env.NODE_ENV === undefined;
import.meta.env.NODE_ENV === undefined;
// 다른 환경 변수는 변경하지 않습니다.
import.meta.env.MODE === 'development';
TIP
값을 단순히 할당하여 변경할 수도 있지만, vi.unstubAllEnvs
를 사용하여 이전 값을 복원할 수는 없습니다.
import.meta.env.MODE = 'test';
vi.unstubAllEnvs
- 타입:
() => Vitest
vi.stubEnv
로 변경된 모든 import.meta.env
및 process.env
값을 복원합니다. 처음 호출될 때 Vitest는 원본 값을 기억하고 unstubAllEnvs
가 다시 호출될 때까지 저장합니다.
import { vi } from 'vitest';
// `process.env.NODE_ENV` 및 `import.meta.env.NODE_ENV`는
// `stubEnv` 호출 전에는 "development"입니다.
vi.stubEnv('NODE_ENV', 'production');
process.env.NODE_ENV === 'production';
import.meta.env.NODE_ENV === 'production';
vi.stubEnv('NODE_ENV', 'staging');
process.env.NODE_ENV === 'staging';
import.meta.env.NODE_ENV === 'staging';
vi.unstubAllEnvs();
// 첫 번째 "stubEnv" 호출 전에 저장된 값으로 복원됩니다.
process.env.NODE_ENV === 'development';
import.meta.env.NODE_ENV === 'development';
vi.stubGlobal
- 타입:
(name: string | number | symbol, value: unknown) => Vitest
전역 변수의 값을 변경합니다. vi.unstubAllGlobals
를 호출하여 원래 값을 복원할 수 있습니다.
import { vi } from 'vitest';
// `innerWidth`는 `stubGlobal` 호출 전에는 "0"입니다.
vi.stubGlobal('innerWidth', 100);
innerWidth === 100;
globalThis.innerWidth === 100;
// jsdom 또는 happy-dom을 사용하는 경우
window.innerWidth === 100;
TIP
값을 단순히 globalThis
또는 window
(jsdom 또는 happy-dom 환경을 사용하는 경우)에 할당하여 변경할 수도 있지만, vi.unstubAllGlobals
를 사용하여 원래 값을 복원할 수는 없습니다.
globalThis.innerWidth = 100;
// jsdom 또는 happy-dom을 사용하는 경우
window.innerWidth = 100;
vi.unstubAllGlobals
- 타입:
() => Vitest
vi.stubGlobal
로 변경된 globalThis
/global
(및 window
/top
/self
/parent
, jsdom 또는 happy-dom 환경을 사용하는 경우)의 모든 전역 값을 복원합니다. 처음 호출될 때 Vitest는 원본 값을 기억하고 unstubAllGlobals
가 다시 호출될 때까지 저장합니다.
import { vi } from 'vitest';
const Mock = vi.fn();
// IntersectionObserver는 "stubGlobal" 호출 전에는 "undefined"입니다.
vi.stubGlobal('IntersectionObserver', Mock);
IntersectionObserver === Mock;
global.IntersectionObserver === Mock;
globalThis.IntersectionObserver === Mock;
// jsdom 또는 happy-dom을 사용하는 경우
window.IntersectionObserver === Mock;
vi.unstubAllGlobals();
globalThis.IntersectionObserver === undefined;
'IntersectionObserver' in globalThis === false;
// 정의되지 않았으므로 ReferenceError가 발생합니다.
IntersectionObserver === undefined;
가짜 타이머
이 섹션에서는 가짜 타이머 사용 방법에 대해 설명합니다.
vi.advanceTimersByTime
- 타입:
(ms: number) => Vitest
이 메서드는 지정된 밀리초가 경과하거나 큐가 비워질 때까지(둘 중 먼저 발생하는 시점까지) 시작된 모든 타이머를 호출합니다.
let i = 0;
setInterval(() => console.log(++i), 50);
vi.advanceTimersByTime(150);
// 로그: 1
// 로그: 2
// 로그: 3
vi.advanceTimersByTimeAsync
- 타입:
(ms: number) => Promise<Vitest>
이 메서드는 지정된 밀리초가 경과하거나 큐가 비워질 때까지(둘 중 먼저 발생하는 시점까지) 시작된 모든 타이머를 호출합니다. 여기에는 비동기적으로 설정된 타이머도 포함됩니다.
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);
await vi.advanceTimersByTimeAsync(150);
// 로그: 1
// 로그: 2
// 로그: 3
vi.advanceTimersToNextTimer
- 타입:
() => Vitest
다음 사용 가능한 타이머를 호출합니다. 각 타이머 호출 사이에 단언을 수행하는 데 유용합니다. 타이머를 직접 관리하기 위해 체인 방식으로 호출할 수 있습니다.
let i = 0;
setInterval(() => console.log(++i), 50);
vi.advanceTimersToNextTimer() // 로그: 1
.advanceTimersToNextTimer() // 로그: 2
.advanceTimersToNextTimer(); // 로그: 3
vi.advanceTimersToNextTimerAsync
- 타입:
() => Promise<Vitest>
다음 사용 가능한 타이머를 호출하고, 비동기적으로 설정된 경우 해결될 때까지 기다립니다. 각 타이머 호출 사이에 단언을 수행하는 데 유용합니다.
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);
await vi.advanceTimersToNextTimerAsync(); // 로그: 1
expect(console.log).toHaveBeenCalledWith(1);
await vi.advanceTimersToNextTimerAsync(); // 로그: 2
await vi.advanceTimersToNextTimerAsync(); // 로그: 3
vi.advanceTimersToNextFrame 2.1.0+
- 타입:
() => Vitest
vi.advanceTimersByTime
과 유사하지만, requestAnimationFrame
으로 현재 예약된 콜백을 실행하는 데 필요한 밀리초만큼 타이머를 진행합니다.
let frameRendered = false;
requestAnimationFrame(() => {
frameRendered = true;
});
vi.advanceTimersToNextFrame();
expect(frameRendered).toBe(true);
vi.getTimerCount
- 타입:
() => number
대기 중인 타이머의 개수를 반환합니다.
vi.clearAllTimers
실행 예정인 모든 타이머를 제거합니다. 이 타이머는 앞으로 절대 실행되지 않습니다.
vi.getMockedSystemTime
- 타입:
() => Date | null
모의된 현재 날짜를 반환합니다. 날짜가 모의되지 않은 경우 메서드는 null
을 반환합니다.
vi.getRealSystemTime
- 타입:
() => number
vi.useFakeTimers
를 사용할 때 Date.now
호출은 모의됩니다. 실제 시간을 밀리초 단위로 가져와야 한다면 이 함수를 호출할 수 있습니다.
vi.runAllTicks
- 타입:
() => Vitest
process.nextTick
에 의해 큐에 추가된 모든 마이크로태스크를 호출합니다. 이는 자체적으로 예약된 모든 마이크로태스크도 실행합니다.
vi.runAllTimers
- 타입:
() => Vitest
이 메서드는 타이머 큐가 비워질 때까지 시작된 모든 타이머를 호출합니다. 즉, runAllTimers
중에 호출된 모든 타이머가 실행됩니다. 무한 간격이 있는 경우 10,000번 시도 후 예외가 발생합니다(fakeTimers.loopLimit
으로 구성 가능).
let i = 0;
setTimeout(() => console.log(++i));
const interval = setInterval(() => {
console.log(++i);
if (i === 3) {
clearInterval(interval);
}
}, 50);
vi.runAllTimers();
// 로그: 1
// 로그: 2
// 로그: 3
vi.runAllTimersAsync
- 타입:
() => Promise<Vitest>
이 메서드는 타이머 큐가 비워질 때까지 시작된 모든 타이머를 비동기적으로 호출합니다. 즉, runAllTimersAsync
중에 호출된 모든 타이머가 비동기 타이머라도 실행됩니다. 무한 간격이 있는 경우, 10,000번 시도 후 예외가 발생합니다(fakeTimers.loopLimit
으로 구성 가능).
setTimeout(async () => {
console.log(await Promise.resolve('result'));
}, 100);
await vi.runAllTimersAsync();
// 로그: result
vi.runOnlyPendingTimers
- 타입:
() => Vitest
이 메서드는 vi.useFakeTimers
호출 이후에 시작된 모든 타이머를 호출합니다. 이 메서드 호출 중에 시작된 타이머는 실행하지 않습니다.
let i = 0;
setInterval(() => console.log(++i), 50);
vi.runOnlyPendingTimers();
// 로그: 1
vi.runOnlyPendingTimersAsync
- 타입:
() => Promise<Vitest>
이 메서드는 vi.useFakeTimers
호출 이후에 시작된 모든 타이머를 비동기적으로 호출하며, 비동기 타이머도 포함합니다. 이 메서드 호출 중에 시작된 타이머는 실행하지 않습니다.
setTimeout(() => {
console.log(1);
}, 100);
setTimeout(() => {
Promise.resolve().then(() => {
console.log(2);
setInterval(() => {
console.log(3);
}, 40);
});
}, 10);
await vi.runOnlyPendingTimersAsync();
// 로그: 2
// 로그: 3
// 로그: 3
// 로그: 1
vi.setSystemTime
- 타입:
(date: string | number | Date) => void
가짜 타이머가 활성화된 경우, 이 메서드는 사용자가 시스템 시계를 변경하는 것을 시뮬레이션합니다(hrtime
, performance.now
또는 new Date()
와 같은 날짜 관련 API에 영향을 미칩니다). 하지만 타이머는 실행하지 않습니다. 가짜 타이머가 활성화되지 않은 경우, 이 메서드는 Date.*
호출만 모킹합니다.
현재 날짜에 의존하는 모든 것을 테스트해야 할 때 유용합니다. 예를 들어 코드 내의 Luxon 호출과 같은 경우입니다.
Date
와 동일한 문자열 및 숫자 인수를 허용합니다.
const date = new Date(1998, 11, 19);
vi.useFakeTimers();
vi.setSystemTime(date);
expect(Date.now()).toBe(date.valueOf());
vi.useRealTimers();
vi.useFakeTimers
- 타입:
(config?: FakeTimerInstallOpts) => Vitest
타이머 모킹을 활성화하려면 이 메서드를 호출해야 합니다. vi.useRealTimers()
가 호출될 때까지 setTimeout
, setInterval
, clearTimeout
, clearInterval
, setImmediate
, clearImmediate
, Date
와 같은 모든 타이머 호출을 래핑합니다.
--pool=forks
를 사용하여 node:child_process
내에서 Vitest를 실행할 때 nextTick
모킹은 지원되지 않습니다. NodeJS는 node:child_process
내부에서 process.nextTick
을 사용하며, 모킹될 때 중단됩니다. --pool=threads
로 Vitest를 실행할 때 nextTick
모킹은 지원됩니다.
구현은 내부적으로 @sinonjs/fake-timers
를 기반으로 합니다.
TIP
vi.useFakeTimers()
는 process.nextTick
및 queueMicrotask
를 자동으로 모킹하지 않습니다. 하지만 toFake
인수에 옵션을 지정하여 활성화할 수 있습니다: vi.useFakeTimers({ toFake: ['nextTick', 'queueMicrotask'] })
.
vi.isFakeTimers
- 타입:
() => boolean
가짜 타이머가 활성화되어 있으면 true
를 반환합니다.
vi.useRealTimers
- 타입:
() => Vitest
타이머가 모두 실행되면 이 메서드를 호출하여 모의 타이머를 원래 구현으로 되돌릴 수 있습니다. 이전에 예약된 모든 타이머는 폐기됩니다.
기타
Vitest가 제공하는 유용한 헬퍼 함수 모음입니다.
vi.waitFor
- 타입:
<T>(callback: WaitForCallback<T>, options?: number | WaitForOptions) => Promise<T>
콜백이 성공적으로 실행될 때까지 기다립니다. 콜백이 오류를 발생시키거나 거부된 Promise를 반환하면, 성공하거나 타임아웃될 때까지 계속 기다립니다.
옵션이 숫자로 설정되면 { timeout: options }
를 설정하는 것과 동일한 효과를 가집니다.
이는 서버를 시작하고 서버가 시작될 때까지 기다려야 하는 경우처럼, 비동기 작업이 완료될 때까지 기다려야 할 때 매우 유용합니다.
import { expect, test, vi } from 'vitest';
import { createServer } from './server.js';
test('서버가 성공적으로 시작되었습니다.', async () => {
const server = createServer();
await vi.waitFor(
() => {
if (!server.isReady) {
throw new Error('서버가 시작되지 않았습니다.');
}
console.log('서버 시작됨');
},
{
timeout: 500, // 기본값은 1000
interval: 20, // 기본값은 50
}
);
expect(server.isReady).toBe(true);
});
비동기 콜백에도 작동합니다.
// @vitest-environment jsdom
import { expect, test, vi } from 'vitest';
import { getDOMElementAsync, populateDOMAsync } from './dom.js';
test('요소가 DOM에 존재합니다.', async () => {
// DOM 채우기 시작
populateDOMAsync();
const element = await vi.waitFor(
async () => {
// 요소가 존재할 때까지 요소를 가져오려고 시도합니다.
const element = (await getDOMElementAsync()) as HTMLElement | null;
expect(element).toBeTruthy();
expect(element.dataset.initialized).toBeTruthy();
return element;
},
{
timeout: 500, // 기본값은 1000
interval: 20, // 기본값은 50
}
);
expect(element).toBeInstanceOf(HTMLElement);
});
vi.useFakeTimers
가 사용되면 vi.waitFor
는 각 확인 콜백에서 자동으로 vi.advanceTimersByTime(interval)
을 호출합니다.
vi.waitUntil
- 타입:
<T>(callback: WaitUntilCallback<T>, options?: number | WaitUntilOptions) => Promise<T>
이것은 vi.waitFor
와 유사하지만, 콜백이 오류를 발생시키면 즉시 실행이 중단되고 오류 메시지가 수신됩니다. 콜백이 거짓 값을 반환하면, 참 값이 반환될 때까지 다음 확인이 계속됩니다. 이는 다음 단계를 수행하기 전에 무언가가 존재할 때까지 기다려야 할 때 유용합니다.
아래 예시를 참조하십시오. vi.waitUntil
을 사용하여 요소가 페이지에 나타날 때까지 기다린 다음, 해당 요소로 작업을 수행할 수 있습니다.
import { expect, test, vi } from 'vitest';
test('요소가 올바르게 렌더링됩니다.', async () => {
const element = await vi.waitUntil(() => document.querySelector('.element'), {
timeout: 500, // 기본값은 1000
interval: 20, // 기본값은 50
});
// 요소로 무언가를 합니다.
expect(element.querySelector('.element-child')).toBeTruthy();
});
vi.hoisted
- 타입:
<T>(factory: () => T) => T
ES 모듈의 모든 정적 import
문은 파일 맨 위로 호이스팅되므로, 임포트 전에 정의된 모든 코드는 실제로는 임포트가 평가된 후에 실행됩니다.
그러나 모듈을 임포트하기 전에 날짜 모킹과 같은 일부 부수 효과를 호출하는 것이 유용할 수 있습니다.
이러한 제한을 우회하려면 정적 임포트를 다음과 같이 동적 임포트로 다시 작성할 수 있습니다.
callFunctionWithSideEffect()
- import { value } from './some/module.js'
+ const { value } = await import('./some/module.js')
vitest
를 실행할 때 vi.hoisted
메서드를 사용하여 이를 자동으로 수행할 수 있습니다. 내부적으로 Vitest는 정적 임포트를 라이브 바인딩을 유지하면서 동적 임포트로 변환합니다.
- callFunctionWithSideEffect()
import { value } from './some/module.js'
+ vi.hoisted(() => callFunctionWithSideEffect())
임포트 사용 불가
임포트 전에 코드를 실행한다는 것은, 임포트된 변수가 아직 정의되지 않았으므로 접근할 수 없다는 것을 의미합니다.
import { value } from './some/module.js';
vi.hoisted(() => { value }); // 오류 발생
이 코드는 다음 오류를 발생시킵니다.
Cannot access '__vi_import_0__' before initialization
vi.hoisted
내부에서 다른 모듈의 변수에 접근해야 하는 경우 동적 임포트를 사용하십시오.
await vi.hoisted(async () => {
const { value } = await import('./some/module.js');
});
그러나 vi.hoisted
내부에서 무엇이든 임포트하는 것은 권장되지 않습니다. 임포트는 이미 호이스팅되었기 때문입니다. 테스트가 실행되기 전에 무언가를 실행해야 한다면, 임포트된 모듈 자체에서 실행하십시오.
이 메서드는 팩토리에서 반환된 값을 반환합니다. 로컬에서 정의된 변수에 쉽게 접근해야 하는 경우, vi.mock
팩토리에서 이 값을 사용할 수 있습니다.
import { expect, vi } from 'vitest';
import { originalMethod } from './path/to/module.js';
const { mockedMethod } = vi.hoisted(() => {
return { mockedMethod: vi.fn() };
});
vi.mock('./path/to/module.js', () => {
return { originalMethod: mockedMethod };
});
mockedMethod.mockReturnValue(100);
expect(originalMethod()).toBe(100);
이 메서드는 환경이 최상위 await
를 지원하지 않더라도 비동기적으로 호출될 수 있습니다.
const json = await vi.hoisted(async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
return response.json();
});
vi.setConfig
- 타입:
RuntimeConfig
현재 테스트 파일의 설정을 업데이트합니다. 이 메서드는 현재 테스트 파일에 영향을 미치는 설정 옵션만 지원합니다.
vi.setConfig({
allowOnly: true,
testTimeout: 10_000,
hookTimeout: 10_000,
clearMocks: true,
restoreMocks: true,
fakeTimers: {
now: new Date(2021, 11, 19),
// 전체 객체를 지원합니다.
},
maxConcurrency: 10,
sequence: {
hooks: 'stack',
// "sequence.hooks"만 지원합니다.
},
});
vi.resetConfig
- 타입:
RuntimeConfig
이전에 vi.setConfig
가 호출된 경우, 이 메서드는 설정을 원래 상태로 재설정합니다.