Reactアプリにおける単体テストはじめ
こんにちはNofi(のふぃ)と申します。
最近買い物リストのwebサイトを作ったので、せっかくならテストまで書いてみたい!ということで人生で初めて!テストコードを書いてみました。
手動&目視でのテスト自体は業務でやったことあるのですがテストコードは書いたことがなく、本読んだり検索したりして手探りでやっています。変なことをしていたらご指摘いただければ幸いです。
そもそもテストってなんだ?
新卒研修でふわっと単体テストや結合テストの話を聞いただけだったので、改めてテストの種類から調べました。
単体テスト(Unit Testing)
個々の機能やモジュールが、仕様通りに動作することを確認する。
結合テスト(Integration Testing)
複数のモジュールやコンポーネントが正しく連携することを確認する。機能テスト。
総合テスト(End-to-End Testing, E2E)
外部APIやブラウザなど全ての条件を結合するテスト。本番と同等の環境を用意してテストを行う。プログラムが要件通りに動作するかを確認する。ユーザー受け入れテスト。
だんだんと機能を取り込んでいって全体、最後はUIをテストするという流れです。
今回はテストを導入ということで単体テストができるようなセットアップを目指します。
テストツール何使うの?
さて、ここからが本題です。
テスト初心者すぎて「そもそも必要なものがわからない」となったので、とりあえず噂に聞いたVitestを調べました。
Vitestとは、JavaScript/TypeScript向けのテストフレームワークで、軽量で高速なテスト実行が可能です。Viteとの統合を意識して設計されており、Viteを使ったプロジェクトで特に効率的に動作します。
npmtrendsを見ると過去2年間のVitestのインストール数は右肩上がりです。
今回はRemix(Viteエンジン)を使っているので相性も良さそうです。とりあえずVitestから入れてみて、必要そうなものを追加して行く方針にします。
Vitestをインストールする
公式のガイドに従ってVitestのインストールを行なっていきます。
インストールコマンドpnpm install -D vitest
package.jsonに以下を追加します。
{
"scripts": {
"test": "vitest"
}
}
チュートリアルにある足し算する関数とそのテストコードをtsファイルで作成します。とりあえずファイル位置はindexファイルと同じ階層にしました。(なんか決まりとかあるのかな?)
// sum.ts
export function sum(a: number, b: number) {
return a + b;
}
// sum.test.ts
import { expect, test } from "vitest";
import { sum } from "./sum.js";
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
この状態で実行するとテスト成功します。
ちなみに、テスト実行のコマンドをpacage.jsonのscriptsに追加しておくと便利です。
//pacage.json
"scripts": {
"test": "vitest --watch false",
},
pnpm vitest
はデフォルトでwatchモード(ファイルの保存毎にテストを走らせるモード)になっています。(例外としてGitHub ActionsなどのCI環境では一度きりのテストになる)
一度だけ実行させたい人は--watch false
をつけるか、vitest run
のコマンドでpacage.jsonに設定しましょう。
これで、pnpn run test
でテストを実行できます。ツールに左右されなくて覚えやすいです。
ツールのまとめ
さて、チュートリアルもできたことだし、次はテストコードの記事を見ながらボタンコンポーネントをテストしよう!と思ったら、Vitestだけでは足りないことに気づきます。
やってみて初めて知ったのですが、テストのためには3つのツールが最低限必要なようです。
テストランナーであるVitestでテストの記述を行い、jsdomでdomを生成しブラウザを仮想で作ります。VitestやTesting Libraryがこれを利用してDOMを操作・検証(テスト)する流れです。
jsdomをインストールする
最新のjsdomにはNode.js v18以降が必要なので、node -vで
のバージョンを確認してからインストールします。
インストールコマンド
npm install jsdom
そしてvitestConfig.tsに以下を追加します。
test{
environment:jsdom
}
Testing Libraryをインストールする
多くのフレームワークに対応していますが、今回はReactなので@testing-library/reactをinstallします。
npm install --save-dev @testing-library/react @testing-library/dom
Remix ViteプラグインがVitestでは使えない
これでツールは揃った!と思ったらまだ使えなさそうです。
pnpm run test
すると以下のエラーが出てきました。
"Error: Remix Vite plugin can't detect preamble. Something is wrong."
調べてみると同じ内容のエラーでissueが作られていました。
未解決のようで、今のところviteとVitestのconfigファイルを作って、Vitestではvitejs/plugin-reactというプラグインを使うことでReactを認識してテストするのが暫定の解決策のようです。
-
vitejs/plugin-reactをインストールして、vitest.config.tsのpluginsに追加します。
-
Vitestのconfigファイルのplugin項目も変更します。
VitestをRemixプロジェクトで使うと、どっちのための設定だったかわからなくなるので、vite.config.tsとvitest.config.ts、2つとも作って設定を分けたほうが良さそうです。
ただし、vitest.config.tsが優先されてvite.configを上書きするので、vite.config.tsの内容はvitest.config.tsにも書いておきます。
//vitest.config.ts
/// <reference types="vitest" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
test: {
environment: "jsdom",
},
plugins: [react(), tsconfigPaths()],
});
これで一旦動くようになりました。
pnpm run test
すればテストが通った時green、テストが通らなかった時redが出ます。
Testing-libraryのjest-domをインストール
動くようにはなったのですが、もっと書きやすいツールがあると小耳に挟んで、Testing libraryのjest-domを調べました。
インストールコマンドpnpm install --save-dev @testing-library/jest-dom
jest-domには以下のように便利なカスタムマッチャーが多くあります。
expect(screen.getByRole("button").textContent).toBe("追加");
↓
expect(screen.getByRole("button")).toHaveTextContent("追加");
DOMに対するアサーション(DOM要素の状態やプロパティを確認する操作)を自然言語に近い形で記述できるためテストコードの可読性も向上しそうです。
私が少々手こずってしまったjest-domの適用手順は以下です。
- @testing-library/jest-domをinstallする。
- setUpTests.tsを作成してVitest用のjest-domをimportする。
- Vitestの設定ファイルにsetupFailesとしてsetUpTests.tsのファイルをパスを指定する。
以上!単体テストの環境構築完了です!
まとめ📚
ということで!
React関連のアプリをテストする際は、Vitest + jsdom + Testing Libraryの3つと可読性がアップしたければjest-domも入れると良さそうでした。
単体テストの環境構築とお試しコードを書いてみたことで、「テスト書いた方がいいって聞くけど、どんなものなんだ…」という気持ちが少し解消されました。
が、ここからどんなことをテストしていくべきかという問題と、そもそもコンポーネントに分けられていないためテスト書きにくい問題が発生しているので、まだまだ頑張ります!
最後まで読んでくださってありがとうございます。
これからテストをはじめてみる人などのお力になれたら幸いです。
Discussion