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の導入については、開発環境の準備をご覧ください。
プロジェクトを作成する
まず、このチュートリアルに使うプロジェクトを作成します。
shellmkdir vitest-tutorialcd vitest-tutorial
shellmkdir vitest-tutorialcd vitest-tutorial
次の内容でpackage.jsonを作成します。
package.jsonjson{"name": "vitest-tutorial","version": "1.0.0","license": "UNLICENSED","type": "module"}
package.jsonjson{"name": "vitest-tutorial","version": "1.0.0","license": "UNLICENSED","type": "module"}
TypeScriptのインストール
プロジェクトにTypeScriptをインストールします。
shellnpm install -D typescript
shellnpm install -D typescript
次に、tsconfig.jsonを作成します。
tsconfig.jsonjson{"compilerOptions": {"target": "esnext","moduleResolution": "bundler","strict": true,"verbatimModuleSyntax": true,"isolatedModules": true,"skipLibCheck": true}}
tsconfig.jsonjson{"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をサポートする必要がある場合には、es2020やes2019など目的に合わせたバージョンを指定してください。moduleResolution: bundlerは、バンドラーのモジュール解決の仕組みと同じルールをTypeScriptコンパイラーに適用するための設定です。もし、Node.js向けの開発などバンドラーを用いない場合には、この設定を除いてください。isolatedModules: trueは、各ファイルを完全に独立した記述になるように制約する設定で、バンドラーがTS→JSへの変換を安全にできるように保証します。
他の設定は、フロントエンド、バックエンドどちらでもよく使う設定です。
strict: trueは、型のチェックなどを厳しい基準で行い、バグの原因になりやすい記述をエラーとして検出するための設定です。verbatimModuleSyntax: trueは、インポート文が勝手に最適化・削除されるのを防ぎ、コードに書かれた通りのモジュール構文を出力するように強制するための設定です。skipLibCheck: trueは、node_modulesにインストールした外部ライブラリの型定義ファイルのチェックをスキップするための設定です。
Vitestをインストールする
Vitestをプロジェクトにインストールしましょう。
shellnpm install -D vitest
shellnpm 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.tstsimport {test } from "vitest";test ("check", () => {console .log ("Hello, World!");});
check.unit.test.tstsimport {test } from "vitest";test ("check", () => {console .log ("Hello, World!");});
unitについての補足Vitestとしては、.test.tsや.spec.tsで終わるファイルをテストファイルとして認識されますが、実務では単体テスト(unit test)、結合テスト(integration test)、コンポーネントテスト(component test)など、さまざまなレベルのテストを書くことになります。Vitestは、こうしたレベルを横断して利用できます。テストの実行はレベルごとに分けて行えるように備えておくほうがスケールしやすいです。そのためには、ファイル名でレベルを表すことが重要です。このチュートリアルでは、こうした実務的な背景を念頭に置き、単体テストのテストファイル名には.unitをつけることにしています。
ファイルを保存したら、vitestコマンドを実行してみてください。
shellnpx vitest
shellnpx 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 quitVitestはデフォルトでウォッチモードで起動します。ウォッチモードでは、ファイルを保存するたびに自動でテストを実行してくれます。試しに、console.logの行を変更してみてください。
check.unit.test.tstsimport {test } from "vitest";test ("check", () => {console .log ("Hello, Vitest!");});
check.unit.test.tstsimport {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のテスト対象コードを書いて、それをテストしていきます。
具体的には、次のような簡単な関数のテストを書くことを例に進めていきます。
tsfunctionisZero (value : number): boolean {returnvalue === 0;}
tsfunctionisZero (value : number): boolean {returnvalue === 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.tstsfunctionisZero (value : number): boolean {returnvalue === 0;}
is-zero.tstsfunctionisZero (value : number): boolean {returnvalue === 0;}
このままではisZero関数はテストできません。テストコードからこの関数を呼び出せるようにするには、関数をエクスポートする必要があります。関数をエクスポートするために、functionの前にexportキーワードを追加してください。
is-zero.tstsexport functionisZero (value : number): boolean {returnvalue === 0;}
is-zero.tstsexport functionisZero (value : number): boolean {returnvalue === 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.tstsimport {expect ,test } from "vitest";import {isZero } from "./is-zero";
is-zero.unit.test.tstsimport {expect ,test } from "vitest";import {isZero } from "./is-zero";
次に、1つ目のテストケースを追加します。このテストケースは、isZero関数に0を渡したらtrueが返るかをチェックするものです。
is-zero.unit.test.tstsimport {expect ,test } from "vitest";import {isZero } from "./is-zero";test ("0を渡したらtrueになること", () => {constresult =isZero (0);expect (result ).toBe (true);});
is-zero.unit.test.tstsimport {expect ,test } from "vitest";import {isZero } from "./is-zero";test ("0を渡したらtrueになること", () => {constresult =isZero (0);expect (result ).toBe (true);});
Vitestではexpect関数とマッチャーを使い、結果が期待する値になっているかを記述します。マッチャーは、expect関数の戻り値に生えているメソッドです。上の例では、toBeがマッチャーになります。このメソッドの引数には期待値を書きます。上のテストケースでは、trueが期待値なので、toBe(true)と記述しています。
toBeマッチャーは、JavaScriptの厳密等価比較(===)と同じです。したがって、expect(result).toBe(true)は内部的にresult === trueかを評価します。もし、この評価が真なら、テストは合格します。逆に、偽ならテストは不合格となります。
マッチャーは、toBe以外にもさまざまなものがあります。このチュートリアルでは細かく解説しないので、詳しく知りたい方は、公式ドキュメントのリファレンスをご覧ください。
テストを実行する
1つ目のテストケースができたので、Vitestでテストを実行してみましょう。
shellnpx vitest
shellnpx 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.tstsimport {expect ,test } from "vitest";import {isZero } from "./is-zero";test ("0を渡したらtrueになること", () => {constresult =isZero (0);expect (result ).toBe (true);});test ("1を渡したらfalseになること", () => {constresult =isZero (1);expect (result ).toBe (false);});
is-zero.unit.test.tstsimport {expect ,test } from "vitest";import {isZero } from "./is-zero";test ("0を渡したらtrueになること", () => {constresult =isZero (0);expect (result ).toBe (true);});test ("1を渡したらfalseになること", () => {constresult =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コマンドを使います。
shellnpx vitest run
shellnpx 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)