メインコンテンツまでスキップ

Vitestでテストを書こう

このチュートリアルでは、テストフレームワーク「Vitest」を使い、ユニットテストをTypeScriptで書くことを学びます。

本章で学べること

本章では、簡単な関数のテストをVitestで書くことを目標に、次のことを学びます。

  • Vitestを使ってTypeScriptの関数をテストする方法
  • Vitestの導入方法
  • Vitestでのテストの書き方
  • テストの実行方法
  • 結果の見方

本章の目的はVitestを完全に理解することではありません。むしろ、Vitestがどういったものなのか、その雰囲気を実際に体験することに主眼を置いています。そのため、内容はかなり最低限のものとなりますが、少しの時間でVitestを試してみれるシンプルな内容にまとまっています。ぜひ手を動かしてみてください。

Vitestとは

Vitestとは、高速な実行速度と直感的な操作性を兼ね備えたモダンなJavaScriptテスティングフレームワークです。TypeScriptやESモジュール、JSXなどモダンなスタックを標準でサポートします。フロントエンドのUIテストだけでなく、ロジックの検証やサーバーサイド開発など、JavaScript/TypeScriptを用いるあらゆるプロジェクトで汎用的に利用できるのが特徴です。

このチュートリアルに必要なもの

このチュートリアルで必要なものは次のとおりです。

  • Node.js v24以上
  • npm v11以上 (Node.jsに同梱)

Node.jsの導入については、開発環境の準備をご覧ください。

プロジェクトを作成する

まず、このチュートリアルに使うプロジェクトを作成します。

shell
mkdir vitest-tutorial
cd vitest-tutorial
shell
mkdir vitest-tutorial
cd vitest-tutorial

次の内容でpackage.jsonを作成します。

package.json
json
{
"name": "vitest-tutorial",
"version": "1.0.0",
"license": "UNLICENSED",
"type": "module"
}
package.json
json
{
"name": "vitest-tutorial",
"version": "1.0.0",
"license": "UNLICENSED",
"type": "module"
}

TypeScriptのインストール

プロジェクトにTypeScriptをインストールします。

shell
npm install -D typescript
shell
npm install -D typescript

次に、tsconfig.jsonを作成します。

tsconfig.json
json
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "bundler",
"strict": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true
}
}
tsconfig.json
json
{
"compilerOptions": {
"target": "esnext",
"moduleResolution": "bundler",
"strict": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true
}
}

このチュートリアルでは、tscを使ってコンパイルすることがないため、tsconfig.jsonを用意する目的はエディターにコンパイル設定を伝えることです。

コンパイルオプションについての補足

上のtsconfig.jsonは、Next.jsやReactなどバンドラーを用いる一般的なフロントエンドの開発を念頭に置いた設定になっています。ちなみに、Vitestもバンドラーを内蔵しています。フロントエンドの開発でVitestを使う場合は、この設定が多くのケースに対応できるはずなので、特に気にせず読み飛ばしてもかまいませんが、Node.jsサーバーサイドアプリケーションや、Node.js向けのライブラリの開発などバンドラーを用いない場合には、注意点がいくつかあります。

  • target: esnextは、最新のECMAScriptの文法をサポートするための設定です。多くのバンドラーは最新のECMAScriptをサポートしています。もし、バンドラーを用いずに古いバージョンのJavaScriptをサポートする必要がある場合には、es2020es2019など目的に合わせたバージョンを指定してください。
  • moduleResolution: bundlerは、バンドラーのモジュール解決の仕組みと同じルールをTypeScriptコンパイラーに適用するための設定です。もし、Node.js向けの開発などバンドラーを用いない場合には、この設定を除いてください。
  • isolatedModules: trueは、各ファイルを完全に独立した記述になるように制約する設定で、バンドラーがTS→JSへの変換を安全にできるように保証します。

他の設定は、フロントエンド、バックエンドどちらでもよく使う設定です。

  • strict: trueは、型のチェックなどを厳しい基準で行い、バグの原因になりやすい記述をエラーとして検出するための設定です。
  • verbatimModuleSyntax: trueは、インポート文が勝手に最適化・削除されるのを防ぎ、コードに書かれた通りのモジュール構文を出力するように強制するための設定です。
  • skipLibCheck: trueは、node_modulesにインストールした外部ライブラリの型定義ファイルのチェックをスキップするための設定です。

Vitestをインストールする

Vitestをプロジェクトにインストールしましょう。

shell
npm install -D vitest
shell
npm install -D vitest

チェックポイント

ここまでに作成したファイルに漏れがないか確認しましょう。

text
├── node_modules ... vitestやtypescriptがインストールされたフォルダ
├── package-lock.json
├── package.json
└── tsconfig.json ... TypeScriptの設定ファイル
text
├── node_modules ... vitestやtypescriptがインストールされたフォルダ
├── package-lock.json
├── package.json
└── tsconfig.json ... TypeScriptの設定ファイル

Vitestが動くかを確認する

ここでは、実際のテストコードを書く前に、Vitestでテストコードが実行できる状態になっているかを、動作確認用のテストファイルを作って確かめます。

Vitestで実行できるテストファイルには命名規則があります。ファイル名が.test.tsまたは.spec.tsで終わるものが、テストファイルになります。動作確認用のファイルとして、check.unit.test.tsを作ってください。内容は次のようにします。

check.unit.test.ts
ts
import { test } from "vitest";
 
test("check", () => {
console.log("Hello, World!");
});
check.unit.test.ts
ts
import { test } from "vitest";
 
test("check", () => {
console.log("Hello, World!");
});
unitについての補足

Vitestとしては、.test.ts.spec.tsで終わるファイルをテストファイルとして認識されますが、実務では単体テスト(unit test)、結合テスト(integration test)、コンポーネントテスト(component test)など、さまざまなレベルのテストを書くことになります。Vitestは、こうしたレベルを横断して利用できます。テストの実行はレベルごとに分けて行えるように備えておくほうがスケールしやすいです。そのためには、ファイル名でレベルを表すことが重要です。このチュートリアルでは、こうした実務的な背景を念頭に置き、単体テストのテストファイル名には.unitをつけることにしています。

ファイルを保存したら、vitestコマンドを実行してみてください。

shell
npx vitest
shell
npx vitest

すると、次のようにテストが実行されます。ターミナルに「Hello, World!」という出力とともに、実行したテストファイル名や実行時間などが表示されます。

 DEV  v4.0.16 /path/to/vitest-tutorial

stdout | check.unit.test.ts > check
Hello, World!

  check.unit.test.ts (1 test) 2ms
    check 1ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  22:07:32
   Duration  276ms (transform 208ms, setup 0ms, import 214ms, tests 2ms, environment 0ms)

 PASS  Waiting for file changes...
       press h to show help, press q to quit

Vitestはデフォルトでウォッチモードで起動します。ウォッチモードでは、ファイルを保存するたびに自動でテストを実行してくれます。試しに、console.logの行を変更してみてください。

check.unit.test.ts
ts
import { test } from "vitest";
 
test("check", () => {
console.log("Hello, Vitest!");
});
check.unit.test.ts
ts
import { test } from "vitest";
 
test("check", () => {
console.log("Hello, Vitest!");
});

テストが自動で再実行された結果、ターミナルには「Hello, Vitest!」と表示されたはずです。

 RERUN  check.unit.test.ts x1 

stdout | check.unit.test.ts > check
Hello, Vitest!

  check.unit.test.ts (1 test) 1ms
    check 1ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  22:10:32
   Duration  7ms

 PASS  Waiting for file changes...
       press h to show help, press q to quit

問題なく実行されていることが確認できたら、Q を押してVitestを終了し、check.unit.test.tsを削除してください。

このチュートリアルでテストする関数

ここからは、TypeScriptのテスト対象コードを書いて、それをテストしていきます。

具体的には、次のような簡単な関数のテストを書くことを例に進めていきます。

ts
function isZero(value: number): boolean {
return value === 0;
}
ts
function isZero(value: number): boolean {
return value === 0;
}

このisZero関数は、数値がゼロかどうかを判定するものです。

テスト対象のファイルを作る

まず、この関数を書いたファイルを作ります。ファイル名はis-zero.tsにしてください。このファイルを作ると、プロジェクトのファイル構成は次のようになります。

text
├── is-zero.ts ... テスト対象ファイル
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json
text
├── is-zero.ts ... テスト対象ファイル
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json

is-zero.tsの内容は次のようにします。

is-zero.ts
ts
function isZero(value: number): boolean {
return value === 0;
}
is-zero.ts
ts
function isZero(value: number): boolean {
return value === 0;
}

このままではisZero関数はテストできません。テストコードからこの関数を呼び出せるようにするには、関数をエクスポートする必要があります。関数をエクスポートするために、functionの前にexportキーワードを追加してください。

is-zero.ts
ts
export function isZero(value: number): boolean {
return value === 0;
}
is-zero.ts
ts
export function isZero(value: number): boolean {
return value === 0;
}

テストコードを書く

上のisZero関数をテストするコードを書きます。

テストコードはテスト対象と別のファイルに書きます。テストファイルを作りましょう。ファイル名は、テストしたいファイル名に、.test.tsをつけたものにします。テスト対象ファイルがis-zero.tsなので、ここではis-zero.unit.test.tsというファイル名にします。このファイルを作ると、プロジェクトのファイル構成は次のようになります。

text
├── is-zero.ts ... テスト対象ファイル
├── is-zero.unit.test.ts ... テストコードのファイル
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json
text
├── is-zero.ts ... テスト対象ファイル
├── is-zero.unit.test.ts ... テストコードのファイル
├── node_modules
├── package-lock.json
├── package.json
└── tsconfig.json

テスト対象の関数をテストコードで扱うには、まず関数をインポートする必要があります。import文を使って、isZero関数を読み込みましょう。

is-zero.unit.test.ts
ts
import { expect, test } from "vitest";
import { isZero } from "./is-zero";
is-zero.unit.test.ts
ts
import { expect, test } from "vitest";
import { isZero } from "./is-zero";

次に、1つ目のテストケースを追加します。このテストケースは、isZero関数に0を渡したらtrueが返るかをチェックするものです。

is-zero.unit.test.ts
ts
import { expect, test } from "vitest";
import { isZero } from "./is-zero";
 
test("0を渡したらtrueになること", () => {
const result = isZero(0);
expect(result).toBe(true);
});
is-zero.unit.test.ts
ts
import { expect, test } from "vitest";
import { isZero } from "./is-zero";
 
test("0を渡したらtrueになること", () => {
const result = isZero(0);
expect(result).toBe(true);
});

Vitestではexpect関数とマッチャーを使い、結果が期待する値になっているかを記述します。マッチャーは、expect関数の戻り値に生えているメソッドです。上の例では、toBeがマッチャーになります。このメソッドの引数には期待値を書きます。上のテストケースでは、trueが期待値なので、toBe(true)と記述しています。

toBeマッチャーは、JavaScriptの厳密等価比較(===)と同じです。したがって、expect(result).toBe(true)は内部的にresult === trueかを評価します。もし、この評価が真なら、テストは合格します。逆に、偽ならテストは不合格となります。

マッチャーは、toBe以外にもさまざまなものがあります。このチュートリアルでは細かく解説しないので、詳しく知りたい方は、公式ドキュメントのリファレンスをご覧ください。

テストを実行する

1つ目のテストケースができたので、Vitestでテストを実行してみましょう。

shell
npx vitest
shell
npx vitest

テストが成功すると、次のように表示されます。


 DEV  v4.0.16 /path/to/vitest-tutorial

  is-zero.unit.test.ts (1 test) 1ms
    0を渡したらtrueになること 0ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  05:55:55
   Duration  109ms (transform 47ms, setup 0ms, import 52ms, tests 1ms, environment 0ms)

 PASS  Waiting for file changes...
       press h to show help, press q to quit

テストケースを追加する

さらにテストケースを追加してみましょう。今度は、isZero関数に1を渡して、戻り値がfalseになるかをチェックするケースを追加します。

is-zero.unit.test.ts
ts
import { expect, test } from "vitest";
import { isZero } from "./is-zero";
 
test("0を渡したらtrueになること", () => {
const result = isZero(0);
expect(result).toBe(true);
});
 
test("1を渡したらfalseになること", () => {
const result = isZero(1);
expect(result).toBe(false);
});
is-zero.unit.test.ts
ts
import { expect, test } from "vitest";
import { isZero } from "./is-zero";
 
test("0を渡したらtrueになること", () => {
const result = isZero(0);
expect(result).toBe(true);
});
 
test("1を渡したらfalseになること", () => {
const result = isZero(1);
expect(result).toBe(false);
});

テストケースを追加したらテストが再実行され、ターミナルには次のようにテスト数が増えたことが表示されます。

 RERUN  is-zero.unit.test.ts x1 

  is-zero.unit.test.ts (2 tests) 1ms
    0を渡したらtrueになること 0ms
    1を渡したらfalseになること 0ms

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  05:58:37
   Duration  9ms

 PASS  Waiting for file changes...
       press h to show help, press q to quit

以上でVitestを体験してみるチュートリアルは完了です。

補足: 非ウォッチモードでテストを実行する

ウォッチモードではなく、1回だけテストを実行する場合は、vitest runコマンドを使います。

shell
npx vitest run
shell
npx vitest run

テストが成功すると、次のように表示されます。

 RUN  v4.0.16 /path/to/vitest-tutorial

  is-zero.unit.test.ts (2 tests) 1ms
    0を渡したらtrueになること 0ms
    1を渡したらfalseになること 0ms

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  06:04:24
   Duration  102ms (transform 41ms, setup 0ms, import 46ms, tests 1ms, environment 0ms)