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
(例えば、ユーティリティファイルから)は使用できないことを意味します。vitest
からインポートされた vi
で vi.mock
を使用するか、globals
設定オプションを有効にしてください。
Vitest は、セットアップファイル内でインポートされたモジュールをモックしません。これは、テストファイルが実行されるまでにそれらがキャッシュされているためです。テストファイルを実行する前にすべてのモジュールキャッシュをクリアするには、vi.hoisted
内で vi.resetModules()
を呼び出すことができます。
factory
関数が定義されている場合、すべてのインポートはその結果を返します。Vitest はファクトリを一度だけ呼び出し、vi.unmock
または vi.doUnmock
が呼び出されるまで、後続のすべてのインポートに対して結果をキャッシュします。
jest
とは異なり、ファクトリは非同期にすることができます。vi.importActual
を使用するか、ファクトリを最初の引数として渡すヘルパーを使用して、元のモジュールを内部で取得できます。
ファクトリ関数の代わりに spy
プロパティを持つオブジェクトを提供することもできます。spy
が true
の場合、Vitest は通常通りモジュールを自動モックしますが、エクスポートの実装を上書きしません。これは、エクスポートされたメソッドが別のメソッドによって正しく呼び出されたことをアサートしたい場合に便利です。
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
メソッドで文字列の代わりにモジュールプロミスもサポートしています。ファイルが移動されると、パスが更新され、importOriginal
は型を自動的に継承します。このシグネチャを使用すると、ファクトリの戻り値の型が元のモジュールと互換性があることも強制されます(エクスポートはオプションのまま)。
// @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,
// 一部のエクスポートを置き換える
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
デフォルトエクスポートを持つモジュールをモックする場合、返されるファクトリ関数オブジェクト内に 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`からのデフォルトエクスポート
import axios from 'axios';
// incrementは`src/__mocks__/increment.js`からの名前付きエクスポート
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 は元のモジュールをインポートし、そのすべてのエクスポートを自動モックします。適用されるルールについては、アルゴリズムを参照してください。
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 は最初のレベルの値がモックされているとしか認識しません。オブジェクト全体がモックされている場合は、2番目の引数として { deep: true }
を渡して、TypeScript にそのように伝えることができます。
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()
がモジュールエクスポートをモックするのと同じ方法で、指定されたオブジェクトのプロパティとメソッドを深くモックします。詳細については、自動モックを参照してください。
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()
と同様に、オブジェクトのメソッドまたはゲッター/セッターにスパイを作成します。これはモック関数を返します。
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
Explicit Resource Management をサポートする環境では、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
ブラウザモードでは、エクスポートされたメソッドにスパイすることはできません。代わりに、vi.mock("./file-path.js", { spy: true })
を呼び出すことで、すべてのエクスポートされたメソッドにスパイできます。これにより、すべてのエクスポートがモックされますが、その実装はそのまま維持され、メソッドが正しく呼び出されたかどうかをアサートできます。
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 環境でエクスポートにスパイすることは可能ですが、これは将来変更される可能性があります。
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);
// log: 1
// log: 2
// log: 3
vi.advanceTimersByTimeAsync
- 型:
(ms: number) => Promise<Vitest>
このメソッドは、指定されたミリ秒数が経過するか、キューが空になるか、どちらか早い方まで、開始されたすべてのタイマーを呼び出します。これには非同期に設定されたタイマーも含まれます。
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);
await vi.advanceTimersByTimeAsync(150);
// log: 1
// log: 2
// log: 3
vi.advanceTimersToNextTimer
- 型:
() => Vitest
次に利用可能なタイマーを呼び出します。各タイマー呼び出しの間でアサーションを行うのに便利です。チェーン呼び出しして、自分でタイマーを管理できます。
let i = 0;
setInterval(() => console.log(++i), 50);
vi.advanceTimersToNextTimer() // log: 1
.advanceTimersToNextTimer() // log: 2
.advanceTimersToNextTimer(); // log: 3
vi.advanceTimersToNextTimerAsync
- 型:
() => Promise<Vitest>
次に利用可能なタイマーを呼び出し、非同期に設定されている場合は解決されるまで待機します。各タイマー呼び出しの間でアサーションを行うのに便利です。
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);
await vi.advanceTimersToNextTimerAsync(); // log: 1
expect(console.log).toHaveBeenCalledWith(1);
await vi.advanceTimersToNextTimerAsync(); // log: 2
await vi.advanceTimersToNextTimerAsync(); // log: 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();
// log: 1
// log: 2
// log: 3
vi.runAllTimersAsync
- 型:
() => Promise<Vitest>
このメソッドは、タイマーキューが空になるまで、開始されたすべてのタイマーを非同期に呼び出します。これは、runAllTimersAsync
中に呼び出されたすべてのタイマーが、非同期タイマーであっても発火することを意味します。無限のインターバルがある場合、 10,000 回の試行後にスローされます(fakeTimers.loopLimit
で設定可能)。
setTimeout(async () => {
console.log(await Promise.resolve('result'));
}, 100);
await vi.runAllTimersAsync();
// log: result
vi.runOnlyPendingTimers
- 型:
() => Vitest
このメソッドは、vi.useFakeTimers
呼び出し後に開始されたすべてのタイマーを呼び出します。その呼び出し中に開始されたタイマーは発火しません。
let i = 0;
setInterval(() => console.log(++i), 50);
vi.runOnlyPendingTimers();
// log: 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();
// log: 2
// log: 3
// log: 3
// log: 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>
コールバックが正常に実行されるのを待ちます。コールバックがエラーをスローしたり、拒否されたプロミスを返したりした場合、成功するかタイムアウトするまで待ち続けます。
オプションが数値に設定されている場合、その効果は { 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
が呼び出された場合、これは設定を元の状態にリセットします。