☃️

Reactアプリにおける単体テストはじめ

2025/01/10に公開

こんにちはNofi(のふぃ)と申します。

最近買い物リストのwebサイトを作ったので、せっかくならテストまで書いてみたい!ということで人生で初めて!テストコードを書いてみました。
手動&目視でのテスト自体は業務でやったことあるのですがテストコードは書いたことがなく、本読んだり検索したりして手探りでやっています。変なことをしていたらご指摘いただければ幸いです。

そもそもテストってなんだ?

新卒研修でふわっと単体テストや結合テストの話を聞いただけだったので、改めてテストの種類から調べました。

単体テスト(Unit Testing)
 個々の機能やモジュールが、仕様通りに動作することを確認する。

結合テスト(Integration Testing)
 複数のモジュールやコンポーネントが正しく連携することを確認する。機能テスト。

総合テスト(End-to-End Testing, E2E)
 外部APIやブラウザなど全ての条件を結合するテスト。本番と同等の環境を用意してテストを行う。プログラムが要件通りに動作するかを確認する。ユーザー受け入れテスト。

だんだんと機能を取り込んでいって全体、最後はUIをテストするという流れです。
今回はテストを導入ということで単体テストができるようなセットアップを目指します。

テストツール何使うの?

さて、ここからが本題です。
テスト初心者すぎて「そもそも必要なものがわからない」となったので、とりあえず噂に聞いたVitestを調べました。
https://vitest.dev/

Vitestとは、JavaScript/TypeScript向けのテストフレームワークで、軽量で高速なテスト実行が可能です。Viteとの統合を意識して設計されており、Viteを使ったプロジェクトで特に効率的に動作します。
npmtrendsを見ると過去2年間のVitestのインストール数は右肩上がりです。

今回はRemix(Viteエンジン)を使っているので相性も良さそうです。とりあえずVitestから入れてみて、必要そうなものを追加して行く方針にします。

Vitestをインストールする

公式のガイドに従ってVitestのインストールを行なっていきます。
https://vitest.dev/guide/
インストールコマンド

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をインストールする

https://github.com/jsdom/jsdom

最新のjsdomにはNode.js v18以降が必要なので、node -vでのバージョンを確認してからインストールします。

インストールコマンド

npm install jsdom

そしてvitestConfig.tsに以下を追加します。

  test{
    environment:jsdom
  }

Testing Libraryをインストールする

https://testing-library.com/

多くのフレームワークに対応していますが、今回はReactなので@testing-library/reactをinstallします。

npm install --save-dev @testing-library/react @testing-library/dom

https://testing-library.com/docs/react-testing-library/intro

Remix ViteプラグインがVitestでは使えない

これでツールは揃った!と思ったらまだ使えなさそうです。
pnpm run testすると以下のエラーが出てきました。

"Error: Remix Vite plugin can't detect preamble. Something is wrong."

調べてみると同じ内容のエラーでissueが作られていました。
https://github.com/remix-run/remix/issues/8982
Remix ViteプラグインはVitestやStorybookなどのVite 構成ファイルを使用する他のViteベースのツールで使用するようには設計されていないそうです。
未解決のようで、今のところviteとVitestのconfigファイルを作って、Vitestではvitejs/plugin-reactというプラグインを使うことでReactを認識してテストするのが暫定の解決策のようです。
https://www.npmjs.com/package/@vitejs/plugin-react

  1. vitejs/plugin-reactをインストールして、vitest.config.tsのpluginsに追加します。

  2. 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を調べました。
https://testing-library.com/docs/ecosystem-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の適用手順は以下です。

  1. @testing-library/jest-domをinstallする。
  2. setUpTests.tsを作成してVitest用のjest-domをimportする。
  3. Vitestの設定ファイルにsetupFailesとしてsetUpTests.tsのファイルをパスを指定する。

以上!単体テストの環境構築完了です!

まとめ📚

ということで!
React関連のアプリをテストする際は、Vitest + jsdom + Testing Libraryの3つと可読性がアップしたければjest-domも入れると良さそうでした。

単体テストの環境構築とお試しコードを書いてみたことで、「テスト書いた方がいいって聞くけど、どんなものなんだ…」という気持ちが少し解消されました。
が、ここからどんなことをテストしていくべきかという問題と、そもそもコンポーネントに分けられていないためテスト書きにくい問題が発生しているので、まだまだ頑張ります!

最後まで読んでくださってありがとうございます。
これからテストをはじめてみる人などのお力になれたら幸いです。

Discussion