テストコンテキスト
Playwright Fixtures に着想を得た Vitest のテストコンテキストは、テストで利用可能なユーティリティ、状態、フィクスチャを定義することを可能にします。
使用方法
各テストコールバックの最初の引数として、テストコンテキストが渡されます。
import { it } from 'vitest';
it('should work', ({ task }) => {
// テストの名前を出力します
console.log(task.name);
});
組み込みテストコンテキスト
task
テストに関するメタデータを含む読み取り専用オブジェクトです。
expect
現在のテストにバインドされた expect
API です。
import { it } from 'vitest';
it('math is easy', ({ expect }) => {
expect(2 + 2).toBe(4);
});
この API は、グローバルな expect
がスナップショットテストを適切に追跡できないため、スナップショットテストを並行して実行する際に特に役立ちます。
import { it } from 'vitest';
it.concurrent('math is easy', ({ expect }) => {
expect(2 + 2).toMatchInlineSnapshot();
});
it.concurrent('math is hard', ({ expect }) => {
expect(2 * 2).toMatchInlineSnapshot();
});
skip
function skip(note?: string): never;
function skip(condition: boolean, note?: string): void;
後続のテスト実行をスキップし、テストをスキップ済みとしてマークします。
import { expect, it } from 'vitest';
it('math is hard', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
Vitest 3.1 以降、条件付きでテストをスキップするための真偽値パラメータを受け入れます。
it('math is hard', ({ skip, mind }) => {
skip(mind === 'foggy');
expect(2 + 2).toBe(5);
});
annotate
3.2.0+
function annotate(
message: string,
attachment?: TestAttachment
): Promise<TestAnnotation>;
function annotate(
message: string,
type?: string,
attachment?: TestAttachment
): Promise<TestAnnotation>;
レポーターによって表示されるテストアノテーションを追加します。
test('annotations API', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
Vitest によって中断可能な AbortSignal
です。このシグナルは以下の状況で中断されます。
- テストがタイムアウトする
- ユーザーが Ctrl+C でテスト実行を手動でキャンセルした
vitest.cancelCurrentRun
がプログラムで呼び出された- 並行して別のテストが失敗し、
bail
フラグが設定されている
it('stop request when test times out', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
現在のテストにバインドされた onTestFailed
フックです。この API は、テストを並行して実行しており、この特定のテストに対して特別な処理が必要な場合に役立ちます。
onTestFinished
現在のテストにバインドされた onTestFinished
フックです。この API は、テストを並行して実行しており、この特定のテストに対して特別な処理が必要な場合に役立ちます。
テストコンテキストの拡張
Vitest は、テストコンテキストを拡張するための2つの異なる方法を提供します。
test.extend
Playwright と同様に、このメソッドを使用すると、カスタムフィクスチャを持つ独自の test
API を定義し、どこでも再利用できます。
例えば、まず todos
と archive
の2つのフィクスチャを持つ test
コレクターを作成します。
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// 各テスト関数の前にフィクスチャをセットアップします
todos.push(1, 2, 3);
// フィクスチャの値を使用します
await use(todos);
// 各テスト関数の後にフィクスチャをクリーンアップします
todos.length = 0;
},
archive,
});
次に、それをインポートして使用できます。
import { expect } from 'vitest';
import { test } from './my-test.js';
test('add items to todos', ({ todos }) => {
expect(todos.length).toBe(3);
todos.push(4);
expect(todos.length).toBe(4);
});
test('move items from todos to archive', ({ todos, archive }) => {
expect(todos.length).toBe(3);
expect(archive.length).toBe(0);
archive.push(todos.pop());
expect(todos.length).toBe(2);
expect(archive.length).toBe(1);
});
さらにフィクスチャを追加したり、既存のフィクスチャを上書きしたりすることもできます。
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
フィクスチャの初期化
Vitest ランナーは、使用状況に基づいてフィクスチャを適切に初期化し、テストコンテキストに注入します。
import { test as baseTest } from 'vitest';
const test = baseTest.extend<{
todos: number[];
archive: number[];
}>({
todos: async ({ task }, use) => {
await use([1, 2, 3]);
},
archive: [],
});
// todos は実行されません
test('skip', () => {});
test('skip', ({ archive }) => {});
// todos は実行されます
test('run', ({ todos }) => {});
WARNING
test.extend()
をフィクスチャと共に使用する場合、フィクスチャ関数とテスト関数の両方で、常にオブジェクト分割代入パターン { todos }
を使用してコンテキストにアクセスするようにしてください。
test('context must be destructured', (context) => {
expect(context.todos.length).toBe(2)
})
test('context must be destructured', ({ todos }) => {
expect(todos.length).toBe(2)
})
自動フィクスチャ
Vitest はフィクスチャのタプル構文もサポートしており、各フィクスチャにオプションを渡すことができます。例えば、テストで使用されていなくても、フィクスチャを明示的に初期化するために使用できます。
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// この関数は実行されます
setup();
await use();
teardown();
},
{ auto: true }, // 自動フィクスチャとしてマーク
],
});
test('works correctly');
デフォルトフィクスチャ
Vitest 3 以降、異なるプロジェクトで異なる値を提供できます。この機能を有効にするには、オプションに { injected: true }
を渡します。キーがプロジェクト設定で指定されていない場合、デフォルト値が使用されます。
import { test as base } from 'vitest';
const test = base.extend({
url: [
// 設定で "url" が定義されていない場合のデフォルト値
'/default',
// 上書きを許可するためにフィクスチャを "injected" としてマーク
{ injected: true },
],
});
test('works correctly', ({ url }) => {
// "project-new" では url は "/default"
// "project-full" では url は "/full"
// "project-empty" では url は "/empty"
});
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
projects: [
{
test: {
name: 'project-new',
},
},
{
test: {
name: 'project-full',
provide: {
url: '/full',
},
},
},
{
test: {
name: 'project-empty',
provide: {
url: '/empty',
},
},
},
],
},
});
スイートへの値のスコープ設定 3.1.0+
Vitest 3.1 以降、test.scoped
API を使用して、スイートとその子孫ごとにコンテキスト値を上書きできます。
import { test as baseTest, describe, expect } from 'vitest';
const test = baseTest.extend({
dependency: 'default',
dependant: ({ dependency }, use) => use({ dependency }),
});
describe('use scoped values', () => {
test.scoped({ dependency: 'new' });
test('uses scoped value', ({ dependant }) => {
// `dependant` は、このスイート内のすべてのテストにスコープされた
// 新しい上書きされた値を使用します
expect(dependant).toEqual({ dependency: 'new' });
});
describe('keeps using scoped value', () => {
test('uses scoped value', ({ dependant }) => {
// ネストされたスイートは値を継承しました
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('keep using the default values', ({ dependant }) => {
// `dependency` は、.scoped を持つスイートの外では
// デフォルト値を使用しています
expect(dependant).toEqual({ dependency: 'default' });
});
この API は、データベース接続のような動的な変数に依存するコンテキスト値がある場合に特に役立ちます。
const test = baseTest.extend<{
db: Database;
schema: string;
}>({
db: async ({ schema }, use) => {
const db = await createDb({ schema });
await use(db);
await cleanup(db);
},
schema: '',
});
describe('one type of schema', () => {
test.scoped({ schema: 'schema-1' });
// ... tests
});
describe('another type of schema', () => {
test.scoped({ schema: 'schema-2' });
// ... tests
});
スコープごとのコンテキスト 3.2.0+
ファイルごと、またはワーカーごとに一度だけ初期化されるコンテキストを定義できます。これは、オブジェクトパラメータを持つ通常のフィクスチャと同じ方法で初期化されます。
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
値は、フィクスチャオプションに auto: true
がない限り、テストから最初にアクセスされたときに初期化されます。この場合、値はテストが実行される前に初期化されます。
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// 常にテストの前にこのフックを実行します
auto: true,
},
],
});
worker
スコープは、ワーカーごとに一度フィクスチャを実行します。実行中のワーカー数は、さまざまな要因によって異なります。デフォルトでは、すべてのファイルが個別のワーカーで実行されるため、file
スコープと worker
スコープは同じように機能します。
ただし、分離を無効にすると、ワーカーの数は maxWorkers
または poolOptions
設定によって制限されます。
vmThreads
または vmForks
でテストを実行する際に scope: 'worker'
を指定しても、scope: 'file'
と同じように機能することに注意してください。この制限は、すべてのテストファイルが独自の VM コンテキストを持つため、Vitest が一度だけ初期化すると、あるコンテキストが別のコンテキストに漏洩し、多くの参照の不整合(同じクラスのインスタンスが異なるコンストラクタを参照するなど)を引き起こす可能性があるために存在します。
TypeScript
すべてのカスタムコンテキストにフィクスチャ型を提供するには、フィクスチャ型をジェネリックとして渡します。
interface MyFixtures {
todos: number[];
archive: number[];
}
const test = baseTest.extend<MyFixtures>({
todos: [],
archive: [],
});
test('types are defined correctly', ({ todos, archive }) => {
expectTypeOf(todos).toEqualTypeOf<number[]>();
expectTypeOf(archive).toEqualTypeOf<number[]>();
});
型推論
use
関数が呼び出されたときに Vitest が型推論をサポートしていないことに注意してください。test.extend
が呼び出されたときに、ジェネリック型としてコンテキスト型全体を渡すことが常に推奨されます。
import { test as baseTest } from 'vitest';
const test = baseTest.extend<{
todos: number[];
schema: string;
}>({
todos: ({ schema }, use) => use([]),
schema: 'test',
});
test('types are correct', ({
todos, // number[]
schema, // string
}) => {
// ...
});
beforeEach
と afterEach
非推奨
これはコンテキストを拡張する非推奨の方法であり、test
が test.extend
で拡張されている場合は機能しません。
コンテキストは各テストで異なります。beforeEach
および afterEach
フック内でそれらにアクセスして拡張できます。
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// コンテキストを拡張します
context.foo = 'bar';
});
it('should work', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
すべてのカスタムコンテキストにプロパティ型を提供するには、TestContext
型を拡張して以下を追加します。
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
特定の beforeEach
、afterEach
、it
、test
フックにのみプロパティ型を提供したい場合は、型をジェネリックとして渡すことができます。
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// context の型は 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('should work', ({ foo }) => {
// foo の型は 'string'
console.log(foo); // 'bar'
});