【Jest】React / TypeScript のフロントエンド開発に単体テストを導入してみた話

# はじめに
タイトルにある通り、React / TypeScriptのフロントエンド開発に単体テストを導入する機会があったので、得た知見をまとめておきます。

# Jestについて
単体テスト導入にあたって、Jestというテストフレームワークを採用しました。

JestはJavaScriptのテストフレームワークで、TypeScriptやReact、Vueなどでも使えます。
もともとFacebookが開発したテストフレームワークで、JavaScript / TypeScriptの単体テストではトップクラスに人気だと思います。

https://jestjs.io/ja/

# Jestのインストール

今回はTypeScriptを使うことを前提としています。

Jestのインストールについては以下が参考になるかと思います。

https://typescriptbook.jp/tutorials/jest#jest%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B

ちなみに、`npm`を使う場合は、以下のようになるかと思います。

```bash
npm install --save-dev jest ts-jest @types/jest
```

# Jestの設定ファイルを作る

Jestの設定ファイルの作り方は以下のドキュメントが参考になるかと思います。

https://jestjs.io/ja/docs/configuration

方法は3つあります。

１. `ts-node`をインストールして、`jest.config.ts`ファイルを作成して以下のように記述する

``` bash
npm install --save-dev ts-node
```

```typescript:jest.config.ts
import type {Config} from 'jest';

const config: Config = {
  preset: "ts-jest", // presetに"ts-jest"を指定
  testEnvironment: "node", // testEnvironmentに"node"を指定
};

export default config;
```

２. `jest.config.json`を作成して、以下のように記述する

```json:jest.config.json
{
  "preset": "ts-jest",
  "testEnvironment": "node"
}
```

３. `package.json`に`"jest"`キーを追加して記述する

```diff_json:package.json
{
  "name": "project_name",
+ "jest": {
+   "preset": "ts-jest",
+   "testEnvironment": "node"
+ }   
}
```

## 設定内容について
`preset`に`"ts-jest"`を指定すると、TypeScript用の設定をいい感じにしてくれます。

（`preset`を使用せず、`transform`などを設定することでもTypeScriptで使えるようにできると思います。）

`testEnvironment`はテスト環境の設定です。（デフォルトでもnodeが指定されています。）

その他の設定項目について詳しくはドキュメントを参照するのが良いと思います。

https://jestjs.io/ja/docs/configuration


# 基本的なテストコードの書き方

以下の`add`関数のテストコードを書く場合を想定します。

```typescript:add.ts
export const add = (input1: number, input2: number): number => {
  return input1 + input2;
};
```

テストファイルでは、まずテスト対象の関数をimportします。

そして、`test`関数の第一引数にテスト名、第二引数にテストで実行する関数を渡します。

expect(`入力値`).toBe(`期待される出力値`)で検証をしています。また、この部分を「アサーション」と呼ぶそうです。

```typescript:add.test.ts
import { add } from "./add";

test("1と1を足した結果として2を返す", () => {
  expect(add(1, 1)).toBe(2); // アサーション
});
```

また、`test`関数をまとめたい場合、`describe`関数でまとめることができます。

```typescript:add.test.ts
import { add } from "./add";

describe("add関数のテスト", () => {
  test("1と1を足した結果として2を返す", () => {
    expect(add(1, 1)).toBe(2);
  });
  test("-1と-2を足した結果として-3を返す", () => {
    expect(add(-1, -2)).toBe(-3);
  });
});
```

# その他にしたこと
Jest導入の際に、他にしたことを紹介します。

## 拡張機能「Jest Runner」のインストール

以下のドキュメントにあるように、コマンドでテストを実行することもできます

https://jestjs.io/ja/docs/cli

が、いちいちコマンドを叩くのが面倒臭い場合は、拡張機能「Jest Runner」をインストールすることをお勧めします。
「Jest Runner」をインストールすると、テストコード上部に`Run | Debug`というボタンが表示されるようになり、`Run`を押すとその部分のテストを実行することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3640003/dd317366-3d2f-9d08-5919-7c261344f8a5.png)

## テスト環境の変更

「Jestの設定ファイルを作る」のところで、テスト環境（`"testEnvironment"`）に`"node"`を指定したかと思いますが、テスト環境が`"node"`だとフロントエンドのテスト実行の際にうまくいかない可能性があります。
具体的には、テスト対象のコードの中に`document`オブジェクトや`window`オブジェクトなどの`node`が実行できない（ブラウザのみが実行できる）コードがあると、エラーとなりテストが中断されてしまいます。

今回は`jest-environment-jsdom`をインストールし、それを`"testEnvironment"`に指定しました。

`jest-environment-jsdom`とは、仮想的なブラウザ環境をシミュレートするツールで、これをテスト環境に指定すると、テスト対象のコードに`document`オブジェクトや`window`オブジェクトなどが含まれていても、テストをきちんと実行することができます。

```bash
npm install --save-dev jest-environment-jsdom
```

```json:任意のJest設定ファイル
{
  "testEnvironment": "jest-environment-jsdom"
}
```

また、プロジェクト内にフロントエンドとバックエンドのファイルが混在しており、テストファイルごとにテスト環境を変えたい場合は、テストファイルの先頭に以下のようなコメントアウトを書くことで、テストファイルごとにテスト環境を変更できます。

```typescript:foo.test.ts
/**
 * @jest-environment jest-environment-jsdom
 */

 // テストコード
```

詳細はこちら。

https://jestjs.io/docs/configuration#testenvironment-string

# 参考書籍

https://www.shoeisha.co.jp/book/detail/9784798178639