Skip to content
Vitest 1
Main Navigation ガイドAPI設定高度な
1.6.1
0.34.6

日本語

English
简体中文
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
한국어
Italiano
Polski
Türkçe
čeština
magyar

日本語

English
简体中文
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
한국어
Italiano
Polski
Türkçe
čeština
magyar

外観

Sidebar Navigation

ガイド

Vitest の必要性

はじめに

特徴

ワークスペース

コマンドラインインターフェース

テストのフィルタリング

レポーター

カバレッジ

スナップショット

モック

型テスト

Vitest UI

ブラウザモード

ソース内テスト

テストコンテキスト

テスト環境

マッチャー拡張

IDE 連携機能

デバッグ

他のテストランナーとの比較

マイグレーションガイド

よくあるエラー

パフォーマンスの改善

API

テスト API リファレンス

モック関数

Vi

expect

expectTypeOf

assert

assertType

設定

Vitestの設定ファイル管理

Vitestの設定

このページの内容

モック ​

テストを作成する際、内部または外部サービスのモックバージョンが必要になることはよくあります。これは一般的にモッキングと呼ばれます。Vitest は、vi ヘルパーを通じて、このモッキングを支援するユーティリティ関数を提供しています。import { vi } from 'vitest' でインポートするか、(グローバルオプションが有効な場合) グローバル変数としてアクセスできます。

WARNING

各テストの実行前または実行後に、モックをリセットまたは復元し、実行間でモックの状態が変更されないようにしてください。詳細については、mockReset のドキュメントを参照してください。

すぐに使い始めたい場合は、API セクションを参照してください。そうでなければ、このまま読み進めてモックについてより深く理解しましょう。

日付 ​

テストの一貫性を保つために、日付を制御する必要がある場合があります。Vitest は、タイマーとシステム日付を操作するために、@sinonjs/fake-timers パッケージを使用します。特定の API の詳細については、こちらを参照してください。

例 ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const businessHours = [9, 17];

function purchase() {
  const currentHour = new Date().getHours();
  const [open, close] = businessHours;

  if (currentHour > open && currentHour < close) return { message: 'Success' };

  return { message: 'Error' };
}

describe('purchasing flow', () => {
  beforeEach(() => {
    // Vitest にモックされた時間を使用することを通知
    vi.useFakeTimers();
  });

  afterEach(() => {
    // 各テスト実行後に日付を復元
    vi.useRealTimers();
  });

  it('allows purchases within business hours', () => {
    // 営業時間内に時間を設定
    const date = new Date(2000, 1, 1, 13);
    vi.setSystemTime(date);

    // Date.now() へのアクセスは上記で設定された日付を返す
    expect(purchase()).toEqual({ message: 'Success' });
  });

  it('disallows purchases outside of business hours', () => {
    // 営業時間外に時間を設定
    const date = new Date(2000, 1, 1, 19);
    vi.setSystemTime(date);

    // Date.now() へのアクセスは上記で設定された日付を返す
    expect(purchase()).toEqual({ message: 'Error' });
  });
});

関数 ​

関数のモックは、スパイとモックという 2 つの異なるカテゴリに分類できます。

特定の関数が呼び出されたかどうか (および、場合によってはどの引数が渡されたか) を検証するだけでよい場合があります。このような場合、スパイは vi.spyOn() で直接使用できる必要な機能です (詳細はこちらを参照してください)。

ただし、スパイは関数の監視に役立つだけでなく、これらの関数の実装を変更することはできません。関数のモックバージョンを作成する必要がある場合は、vi.fn() を使用できます (詳細はこちらを参照してください)。

関数をモックするためのベースとして Tinyspy を使用していますが、jest との互換性を持たせるために独自のラッパーを用意しています。vi.fn() と vi.spyOn() はどちらも同じメソッドを共有していますが、vi.fn() の戻り値のみが呼び出し可能です。

例 ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

function getLatest(index = messages.items.length - 1) {
  return messages.items[index];
}

const messages = {
  items: [
    { message: 'Simple test message', from: 'Testman' },
    // ...
  ],
  getLatest, // `getter` または `setter` もサポート
};

describe('reading messages', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should get the latest message with a spy', () => {
    const spy = vi.spyOn(messages, 'getLatest');
    expect(spy.getMockName()).toEqual('getLatest');

    expect(messages.getLatest()).toEqual(
      messages.items[messages.items.length - 1]
    );

    expect(spy).toHaveBeenCalledTimes(1);

    spy.mockImplementationOnce(() => 'access-restricted');
    expect(messages.getLatest()).toEqual('access-restricted');

    expect(spy).toHaveBeenCalledTimes(2);
  });

  it('should get with a mock', () => {
    const mock = vi.fn().mockImplementation(getLatest);

    expect(mock()).toEqual(messages.items[messages.items.length - 1]);
    expect(mock).toHaveBeenCalledTimes(1);

    mock.mockImplementationOnce(() => 'access-restricted');
    expect(mock()).toEqual('access-restricted');

    expect(mock).toHaveBeenCalledTimes(2);

    expect(mock()).toEqual(messages.items[messages.items.length - 1]);
    expect(mock).toHaveBeenCalledTimes(3);
  });
});

詳しくはこちら ​

  • Jest のモック関数

グローバル変数 ​

vi.stubGlobal ヘルパーを使用すると、jsdom または node に存在しないグローバル変数をモックできます。これは、グローバル変数の値を globalThis オブジェクトに設定します。

ts
import { vi } from 'vitest';

const IntersectionObserverMock = vi.fn(() => ({
  disconnect: vi.fn(),
  observe: vi.fn(),
  takeRecords: vi.fn(),
  unobserve: vi.fn(),
}));

vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);

// これで `IntersectionObserver` または `window.IntersectionObserver` としてアクセスできます

モジュール ​

モックモジュールを使用すると、他のコードで呼び出されるサードパーティライブラリを監視し、引数や出力をテストしたり、実装を置き換えたりできます。

詳細な API の説明については、vi.mock() API セクションを参照してください。

自動モックアルゴリズム ​

コードがモックされたモジュールをインポートしている場合、そのモジュールに関連付けられた __mocks__ ファイルや factory が存在しない場合、Vitest はモジュール自体を呼び出し、すべてのエクスポートをモックすることで、モジュール自体をモックします。

以下の原則が適用されます。

  • すべての配列は空になります。
  • すべてのプリミティブとコレクションは同じままになります。
  • すべてのオブジェクトはディープクローンされます。
  • すべてのクラスのインスタンスとそのプロトタイプはディープクローンされます。

仮想モジュール ​

Vitest は、Vite の仮想モジュールのモックをサポートしています。これは、Jest での仮想モジュールの扱い方とは異なります。 vi.mock関数にvirtual: trueを渡す代わりに、Vite にモジュールが存在することを伝える必要があります。そうしないと、解析中に失敗します。これを行うには、いくつかの方法があります。

1.エイリアスを提供する

ts
// vitest.config.js
export default {
  test: {
    alias: {
      '$app/forms': resolve('./mocks/forms.js'),
    },
  },
};

2.仮想モジュールを解決するプラグインを提供する

ts
// vitest.config.js
export default {
  plugins: [
    {
      name: 'virtual-modules',
      resolveId(id) {
        if (id === '$app/forms') return 'virtual:$app/forms';
      },
    },
  ],
};

2 番目のアプローチの利点は、さまざまな仮想エントリポイントを動的に作成できることです。 複数の仮想モジュールを 1 つのファイルにリダイレクトすると、それらはすべてvi.mockの影響を受けるため、必ず一意の識別子を使用してください。

モックの落とし穴 ​

同じファイルの他のメソッド内で呼び出されるメソッドへの呼び出しをモックすることはできないことに注意してください。 たとえば、次のコードでは:

ts
export function foo() {
  return 'foo';
}

export function foobar() {
  return `${foo()}bar`;
}

fooメソッドが直接参照されているため、外部からfooメソッドをモックすることはできません。 したがって、このコードはfoobar内のfoo呼び出しには影響しません(ただし、他のモジュール内のfoo呼び出しには影響します)。

ts
import { vi } from 'vitest';
import * as mod from './foobar.js';

// これは元のモジュールの外部の「foo」にのみ影響します
vi.spyOn(mod, 'foo');
vi.mock('./foobar.js', async importOriginal => {
  return {
    ...(await importOriginal<typeof import('./foobar.js')>()),
    // これは元のモジュールの外部の「foo」にのみ影響します
    foo: () => 'mocked',
  };
});

実装を直接foobarメソッドに提供することで、この動作を確認できます。

ts
// foobar.test.js
import * as mod from './foobar.js';

vi.spyOn(mod, 'foo');

// エクスポートされたfooはモックされたメソッドを参照します
mod.foobar(mod.foo);
ts
// foobar.js
export function foo() {
  return 'foo';
}

export function foobar(injectedFoo) {
  return injectedFoo !== foo; // false
}

これは意図された動作です。 モックがそのような方法で関与している場合、通常は悪いコードの兆候です。 コードを複数のファイルにリファクタリングするか、依存性注入などの手法を使用してアプリケーションアーキテクチャを改善することを検討してください。

例 ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Client } from 'pg';
import { failure, success } from './handlers.js';

export function success(data) {}
export function failure(data) {}

export async function getTodos(event, context) {
  const client = new Client({});

  await client.connect();

  try {
    const result = await client.query('SELECT * FROM todos;');

    client.end();

    return success({
      message: `${result.rowCount} item(s) returned`,
      data: result.rows,
      status: true,
    });
  } catch (e) {
    console.error(e.stack);

    client.end();

    return failure({ message: e, status: false });
  }
}

vi.mock('pg', () => {
  const Client = vi.fn();
  Client.prototype.connect = vi.fn();
  Client.prototype.query = vi.fn();
  Client.prototype.end = vi.fn();

  return { Client };
});

vi.mock('./handlers.js', () => {
  return {
    success: vi.fn(),
    failure: vi.fn(),
  };
});

describe('get a list of todo items', () => {
  let client;

  beforeEach(() => {
    client = new Client();
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it('should return items successfully', async () => {
    client.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });

    await getTodos();

    expect(client.connect).toBeCalledTimes(1);
    expect(client.query).toBeCalledWith('SELECT * FROM todos;');
    expect(client.end).toBeCalledTimes(1);

    expect(success).toBeCalledWith({
      message: '0 item(s) returned',
      data: [],
      status: true,
    });
  });

  it('should throw an error', async () => {
    const mError = new Error('Unable to retrieve rows');

    client.query.mockRejectedValueOnce(mError);

    await getTodos();

    expect(client.connect).toBeCalledTimes(1);
    expect(client.query).toBeCalledWith('SELECT * FROM todos;');
    expect(client.end).toBeCalledTimes(1);
    expect(failure).toBeCalledWith({ message: mError, status: false });
  });
});

リクエスト ​

Vitest は Node で実行されるため、ネットワークリクエストのモックは複雑です。Web API は利用できないため、ネットワーク動作を模倣する仕組みが必要です。これを実現するには、Mock Service Worker をお勧めします。これにより、REST と GraphQL の両方のネットワークリクエストをモックでき、フレームワークに依存しません。

Mock Service Worker (MSW) は、テストが行うリクエストをインターセプトすることで機能するため、アプリケーションコードを変更せずに使用できます。ブラウザでは、これは Service Worker API を使用します。Node.js および Vitest の場合、@mswjs/interceptors を使用します。MSW の詳細については、概要を参照してください。

構成 ​

セットアップファイルで以下のように使用できます。

js
import { afterAll, afterEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { HttpResponse, graphql, http } from 'msw';

const posts = [
  {
    userId: 1,
    id: 1,
    title: 'first post title',
    body: 'first post body',
  },
  // ...
];

export const restHandlers = [
  http.get('https://rest-endpoint.example/path/to/posts', () => {
    return HttpResponse.json(posts);
  }),
];

const graphqlHandlers = [
  graphql.query('ListPosts', () => {
    return HttpResponse.json({
      data: { posts },
    });
  }),
];

const server = setupServer(...restHandlers, ...graphqlHandlers);

// すべてのテストの前にサーバーを起動
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

// すべてのテストの後にサーバーを停止
afterAll(() => server.close());

// 各テストの後にハンドラーをリセット (テストの分離に重要)
afterEach(() => server.resetHandlers());

onUnhandleRequest: 'error' でサーバーを構成すると、対応するハンドラがないリクエストがあるたびにエラーがスローされるようになります。

例 ​

MSW を使用した完全に動作する例として、MSW を使用した React テストがあります。

詳しくはこちら ​

MSW にはさらに多くの機能があります。Cookie とクエリパラメータにアクセスしたり、モックエラー応答を定義したりできます。MSW でできることすべてを確認するには、ドキュメントを参照してください。

タイマー ​

タイムアウトやインターバルを含むコードをテストする場合、テストが完了するのを待ったり、タイムアウトさせたりする代わりに、setTimeout および setInterval の呼び出しをモックする「偽の」タイマーを使用することで、テストを高速化できます。

詳細な API の説明については、vi.useFakeTimers API セクションを参照してください。

例 ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

function executeAfterTwoHours(func) {
  setTimeout(func, 1000 * 60 * 60 * 2);
}

function executeEveryMinute(func) {
  setInterval(func, 1000 * 60);
}

const mock = vi.fn(() => console.log('executed'));

describe('delayed execution', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });
  afterEach(() => {
    vi.restoreAllMocks();
  });
  it('should execute the function', () => {
    executeAfterTwoHours(mock);
    vi.runAllTimers();
    expect(mock).toHaveBeenCalledTimes(1);
  });
  it('should not execute the function', () => {
    executeAfterTwoHours(mock);
    // 2ms 進めても関数はトリガーされない
    vi.advanceTimersByTime(2);
    expect(mock).not.toHaveBeenCalled();
  });
  it('should execute every minute', () => {
    executeEveryMinute(mock);
    vi.advanceTimersToNextTimer();
    expect(mock).toHaveBeenCalledTimes(1);
    vi.advanceTimersToNextTimer();
    expect(mock).toHaveBeenCalledTimes(2);
  });
});

チートシート ​

INFO

以下の例の vi は、vitest から直接インポートされています。 設定 で globals を true に設定すると、グローバル変数として使用することも可能です。

次のことを行いたい

method をスパイする ​

ts
const instance = new SomeClass();
vi.spyOn(instance, 'method');

エクスポートされた変数をモックする ​

js
// some-path.js
export const getter = 'variable';
ts
// some-path.test.ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked');

エクスポートされた関数をモックする ​

  1. vi.mock を使用した例:

WARNING

vi.mockの呼び出しはファイルの先頭にホイスティングされることを忘れないでください。常にすべてのインポートの前に実行されます。

ts
// ./some-path.js
export function method() {}
ts
import { method } from './some-path.js';

vi.mock('./some-path.js', () => ({
  method: vi.fn(),
}));
  1. vi.spyOn を使用した例:
ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'method').mockImplementation(() => {});

エクスポートされたクラスの実装をモックする ​

  1. vi.mockと.prototypeを使った例:
ts
// some-path.ts
export class SomeClass {}
ts
import { SomeClass } from './some-path.js';

vi.mock('./some-path.js', () => {
  const SomeClass = vi.fn();
  SomeClass.prototype.someMethod = vi.fn();
  return { SomeClass };
});
// SomeClass.mock.instances には SomeClass のインスタンスが格納されます
  1. vi.mock と戻り値を使用した例:
ts
import { SomeClass } from './some-path.js';

vi.mock('./some-path.js', () => {
  const SomeClass = vi.fn(() => ({
    someMethod: vi.fn(),
  }));
  return { SomeClass };
});
// SomeClass.mock.returns には返されたオブジェクトが格納されます
  1. vi.spyOn を使用した例:
ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'SomeClass').mockImplementation(() => {
  // 最初の二つの例から適切なものを選択してください
});

関数から返されたオブジェクトをスパイする ​

  1. キャッシュを使用した例:
ts
// some-path.ts
export function useObject() {
  return { method: () => true };
}
ts
// useObject.js
import { useObject } from './some-path.js';

const obj = useObject();
obj.method();
ts
// useObject.test.js
import { useObject } from './some-path.js';

vi.mock('./some-path.js', () => {
  let _cache;
  const useObject = () => {
    if (!_cache) {
      _cache = {
        method: vi.fn(),
      };
    }
    // useObject() が呼び出されるたびに、
    // 同じオブジェクト参照を返すようになります
    return _cache;
  };
  return { useObject };
});

const obj = useObject();
// obj.method は some-path 内で呼び出されました
expect(obj.method).toHaveBeenCalled();

モジュールの一部をモックする ​

ts
import { mocked, original } from './some-path.js';

vi.mock('./some-path.js', async importOriginal => {
  const mod = await importOriginal<typeof import('./some-path.js')>();
  return {
    ...mod,
    mocked: vi.fn(),
  };
});
original(); // 元の動作
mocked(); // スパイ関数

現在の日付をモックする ​

Date オブジェクトの時間をモックするには、vi.setSystemTime ヘルパー関数を使用できます。この値は、テスト間で自動的にリセットされません。

vi.useFakeTimers を使用すると、Date オブジェクトの時間も変更されることに注意してください。

ts
const mockDate = new Date(2022, 0, 1);
vi.setSystemTime(mockDate);
const now = new Date();
expect(now.valueOf()).toBe(mockDate.valueOf());
// モックされた時間をリセット
vi.useRealTimers();

グローバル変数をモックする ​

グローバル変数は、globalThis に値を割り当てるか、vi.stubGlobal ヘルパーを使用して設定できます。 vi.stubGlobal を使用する場合、unstubGlobals 設定オプションを有効にするか、vi.unstubAllGlobals を呼び出さない限り、テスト間で自動的にリセットされません。

ts
vi.stubGlobal('__VERSION__', '1.0.0');
expect(__VERSION__).toBe('1.0.0');

import.meta.env をモックする ​

  1. 環境変数を変更するには、単に新しい値を割り当てるだけです。

WARNING

環境変数の値は、異なるテスト間で自動的にリセット**されません**。

ts
import { beforeEach, expect, it } from 'vitest';

// beforeEach フックで手動でリセットできます
const originalViteEnv = import.meta.env.VITE_ENV;

beforeEach(() => {
  import.meta.env.VITE_ENV = originalViteEnv;
});

it('changes value', () => {
  import.meta.env.VITE_ENV = 'staging';
  expect(import.meta.env.VITE_ENV).toBe('staging');
});
  1. 値を自動的にリセットしたい場合は、unstubEnvs構成オプションを有効にしてvi.stubEnvヘルパーを使用するか、beforeEachフックでvi.unstubAllEnvs](/api/vi#vi-unstuballenvs)を手動で呼び出すことができます。
ts
import { expect, it, vi } from 'vitest';

// テスト実行前は "VITE_ENV" は "test" です
import.meta.env.VITE_ENV === 'test';

it('changes value', () => {
  vi.stubEnv('VITE_ENV', 'staging');
  expect(import.meta.env.VITE_ENV).toBe('staging');
});

it('他のテストの実行前に値が復元されます', () => {
  expect(import.meta.env.VITE_ENV).toBe('test');
});
ts
// vitest.config.ts
export default {
  test: {
    unstubAllEnvs: true,
  },
};
Pager
前のページスナップショット
次のページ型テスト

MITライセンス の下で公開されています。

Copyright (c) 2024 Mithril Contributors

https://v1.vitest.dev/guide/mocking

MITライセンス の下で公開されています。

Copyright (c) 2024 Mithril Contributors