React × Supabase × Vercel で TODO アプリを作ってみた ② ～ Vitest で単体テスト編～ 🔍

## React × Supabase × Vercel シリーズ

ある程度、独立した内容になっていますが、ご興味あればどうぞ 💁‍♂️

- [① 作ってデプロイ編](https://qiita.com/kamata-bug-factory/items/363a4291fd31a8d34ead)
- ② Vitest で単体テスト編 ← 本記事
- [③ GitHub Actions で CI/CD 編](https://qiita.com/kamata-bug-factory/items/cb7dceacba1d0b58990b)

## はじめに

Create React App (CRA) + React のテストでは Jest を使うのが主流でしたが、Vite + React のテストでは **Vitest** を使うのが定番のようです。

前回 Vite + React で以下の TODO アプリを作りました。

https://github.com/kamata-bug-factory/typescript-todo

こちらのアプリを Vitest でテストしてみます ✊

## ディレクトリ構成

こんな感じになる予定です 🌲

```diff
typescript-todo
├── README.md
└── todo-app
    ├── README.md
    ├── eslint.config.js
    ├── index.html
    ├── package-lock.json
    ├── package.json
    ├── public
    │   └── vite.svg
    ├── src
    │   ├── App.css
    │   ├── App.tsx
    │   ├── assets
    │   │   └── react.svg
    │   ├── components
    │   │   ├── CreateTodoForm
    │   │   │   ├── CreateTodoForm.css
    │   │   │   └── CreateTodoForm.tsx
    │   │   ├── TodoListItem
    │   │   │   ├── TodoListItem.css
    │   │   │   └── TodoListItem.tsx
    │   │   └── ValueViewer
    │   │       ├── ValueViewer.css
    │   │       └── ValueViewer.tsx
    │   ├── hooks
    │   │   └── useTodo.ts
    │   ├── index.css
    │   ├── lib
    │   │   └── supabaseClient.ts
    │   ├── main.tsx
    │   ├── pages
    │   │   └── TodoHome
    │   │       ├── TodoHome.css
    │   │       └── TodoHome.tsx
    │   ├── types
    │   │   └── Todo.ts
    │   ├── utils
+   │   │   ├── TodoUtil.test.ts
    │   │   ├── TodoUtil.ts
    │   │   └── api.ts
    │   └── vite-env.d.ts
    ├── tsconfig.app.json
    ├── tsconfig.json
    ├── tsconfig.node.json
    ├── vite.config.ts
+   └── vitest.config.ts
```

## 手順 🧑‍🏫

### 1. 必要なパッケージのインストール

Vitest をインストールします。

```bash
npm install --save-dev vitest
```

**コンポーネントのレンダリング** や **ユーザーインタラクション** のテストをするなら以下も必要です。

```bash
npm install --save-dev @testing-library/react @testing-library/jest-dom jsdom
```

:::note info
**開発用依存 (devDependencies)** としてインストールするので `--save-dev` オプションをつけます。
:::

### 2. `vitest.config.ts` の設定

Vitest の設定をします。

特にカスタマイズしないなら、おまじない的に以下を書いておけば大丈夫 👌

```ts:vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    environment: 'jsdom',
    globals: true,
  },
});
```

### 3. テストコードを書く

例えば、`TodoUtil` のテストはこんな感じ 💁‍♂️

```ts:src/utils/TodoUtil.test.ts
import { describe, expect, test } from 'vitest';
import { TodoUtil } from './TodoUtil';

const todoItems = [
  { id: 1, text: 'メモリーズ', done: false },
  { id: 2, text: 'メモリーズ・カスタム', done: true },
  { id: 3, text: '三日月ロック その3', done: false },
  { id: 4, text: '夜を駆ける', done: false },
];

describe('filterTodoItems', () => {
  test('キーワードでフィルターできる', () => {
    const result = TodoUtil.filterTodoItems(todoItems, 'メモリーズ', true);
    expect(result).toEqual([
      { id: 1, text: 'メモリーズ', done: false },
      { id: 2, text: 'メモリーズ・カスタム', done: true },
    ]);
  });

  test('完了済みタスクを除外できる', () => {
    const result = TodoUtil.filterTodoItems(todoItems, '', false);
    expect(result).toEqual([
      { id: 1, text: 'メモリーズ', done: false },
      { id: 3, text: '三日月ロック その3', done: false },
      { id: 4, text: '夜を駆ける', done: false },
    ]);
  });

  test('キーワードに一致しない場合は空配列', () => {
    const result = TodoUtil.filterTodoItems(todoItems, '夜に駆ける', true);
    expect(result).toEqual([]);
  });

  test('キーワードも完了除外も指定なしなら全件返す', () => {
    const result = TodoUtil.filterTodoItems(todoItems, '', true);
    expect(result).toEqual([
      { id: 1, text: 'メモリーズ', done: false },
      { id: 2, text: 'メモリーズ・カスタム', done: true },
      { id: 3, text: '三日月ロック その3', done: false },
      { id: 4, text: '夜を駆ける', done: false },
    ]);
  });
});
```

:::note info
ユーティリティ関数のテストなので **React Testing Library (RTL)** などは使っていません。
:::

### 4. 実行

```bash
npx vitest run
```

もしくは `package.json` にスクリプトを追加して

```json:package.json
"scripts": {
  "test": "vitest run"
}
```

```bash
npm run test
```

#### 実行結果

実行結果をペタリ

![スクリーンショット 2025-04-12 8.27.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/ab8e4e35-1aa4-4be1-b86a-57404cff4bce.png)


## おわりに

Vitest はテストランナーなので、テストコードの書き方自体は Jest とそんなに変わらない印象 🤔

CI に組み込むならこんな感じで書くと良さげ？👇

```yaml
- name: Run unit tests
  run: npx vitest run
```

GitHub Actions で CI/CD にチャレンジするときの宿題 ✍️