Slice のテストを書く
この章では TypeScript のテストの書き方の学習も兼ねて、Redux Toolkit で作成した Slice の一部の機能のテストを書いていきたいと思います。
基本的には JavaScript でのテストコードの書き方と同じです。
JavaScript との違いは、TypeScript 用に追加でインストールするライブラリが少し追加されるくらいです。
テストに必要なライブラリをインストールする
今回はテストツールに「Jest」を使います。
以下のコマンドを実行して、TypeScript で Jest を使う準備をします。
npm install -D jest @types/jest ts-jest ts-node
- 公式ドキュメント(「はじめましょう」 > 「TypeScript を使用する」)(今回は ts-jest を利用する)
- TypeScript Deep Dive 日本語版 Jest
Jest の設定ファイルを作成する
以下のコマンドを実行すると、Jest の設定ファイル jest.config.js(jest.config.ts)
を作成するためのプロセスが開始します。
npx jest --init
? Would you like to use Typescript for the configuration file? › (y/N)
→yを打ってエンター(TypeScriptを使っているため)
? Choose the test environment that will be used for testing › - Use arrow-keys. Return to submit
→jsdom (browser-like)を選択してエンター(Webブラウザを使ったアプリとして開発をしているため)
? Do you want Jest to add coverage reports? › (y/N)
→特に何も入力せずエンター(デフォルトでは大文字が選択される。今回の場合はNが選択される(Nの意味はNo))
? Which provider should be used to instrument code for coverage? › - Use arrow-keys. Return to submit.
→v8を選択してエンター(今回はbabelを利用しないため(インストールしないため))
? Automatically clear mock calls, instances, contexts and results before every test? › (y/N)
→特に何も入力せずエンター(デフォルトのNが選択される)
一連のプロセスが終わると、プロジェクトのルートディレクトリに jest.config.ts
が作成されます。(人によっては jest.config.js
かもしれません)
自動生成されたjest.config.ts
の中を見ると、コメントが大量にあって読みにくいので、コメント部分は削除しても良いです。
Jest の設定ファイルを修正する
自動生成されたjest.config.ts
にts-jest
の記述を加えていきます。
export default = {
// [...]
// Replace `ts-jest` with the preset you want to use
// from the above list
preset: 'ts-jest',
}
Jest を実行してみる
まずは package.json の scripts に以下のコードを追記してください。こうすることで npm test
で jest を実行できるようになります。
{
...,
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "jest"
},
...
}
上記の記述をした上で npm test
を実行した際に以下のようなエラーが出たら、エラーメッセージに従って、 jest-environment-jsdom
をインストールします。
npm i -D jest-environment-jsdom
jest-environment-jsdom
をインストールした後、もう一度 npm test
を実行し、以下のようにエラーが出なくなれば OK です。
現状はテストファイルを 1 つも作成していないので、「No tests found, exiting with code 1」と出力されています。
テストファイルを作成する
テストファイルのファイル名にはルールがあります。
上の画像の「testMatch」と書かれている行の黄色文字を読んでいただくとわかりますが、以下のパターンのルールでファイル名を作成する必要があります。
**/__tests__/**/*.[jt]s?(x)
**/?(*.)+(spec|test).[tj]s?(x)
これらが意味するのは、以下のパターンにマッチするファイルテストにファイルになるということを意味しています。
__tests__
ディレクトリより下に作られたjs
,jsx
,ts
,tsx
ファイル__tests__
ディレクトリ関係なく、末尾がファイルのtest.js
,test.jsx
,test.ts
,test.tsx
,spec.js
,spec.jsx
,spec.ts
,spec.tsx
というファイル
今回は todosSlice.ts
のテストファイルとして、todosSlice.ts
と同じ階層に todosSlice.spec.ts
というテストファイルを作成していきます。
テストファイルの中身
テストファイルの中身は以下のように記述してください。
import todosReducer, { create, TodoState } from './todosSlice';import { TodoInput, TodoId } from './types';describe('todos reducer', () => { const initialState: TodoState = { todos: [], displayStatus: 'all', isFetching: false, error: null, }; it('should handle create reducer', () => { const payload: TodoInput = { title: 'title1', body: 'body1', }; const newState = todosReducer(initialState, create(payload)); const todos = newState.todos; expect(todos.length).toEqual(1); expect(typeof todos[0].id).toEqual('string'); expect(todos[0].title).toEqual(payload.title); expect(todos[0].body).toEqual(payload.body); expect(todos[0].status).toEqual('waiting'); expect(todos[0].createdAt).not.toEqual(undefined); expect(todos[0].updatedAt).toEqual(undefined); expect(todos[0].deletedAt).toEqual(undefined); expect(newState).toEqual({ todos, displayStatus: 'all', isFetching: false, error: null, }); });});
このテストコードは reducer の create
が実行されたときに、どのように State の内容が変化している確認しています。
具体的な確認内容は以下の通りです。
- Todo データが 1 件追加されているかを確認している
- 追加された Todo データの各プロパティの値や型が正しいかを確認している
- TodoState の各値(
displayStatus
,isFetching
,error
)が正しいかを確認している
テストを実行する
npm test
を実行すると、以下の画像のように uuid
の部分でエラーが出ている可能性があります。
調べてみると、解決につながる以下の記事を見つけることができました。
こちらの記事を参考にすると、以下の対応をすることで解決したとのことなので、同じようにuuid
の特定のバージョンをインストールするようにします。
- “@types/uuid”: “8.3.0", -> “@types/uuid”: “8.0.1",
- “uuid”: “8.3.1", -> “uuid”: “8.1.0",
以下のコマンド実行してバージョンを同じ記事と同じものを使うようにします。
npm i -D @types/uuid@8.0.1
npm i uuid@8.1.0
上記を試しすとエラーが解決されるかと思います。(僕の環境ではエラーが解決されました)
もしエラーが解決されず、色々と調べても解決できない場合は Discord まで質問をいただけたらと思います。
もう一度テストを実行する
uuid のバージョン問題を解決した上でテストを再度実行すると、以下のようなエラーがでるかもしれません。
エラーメッセージにある「(0 , dayjs_1.default) is not a function
」で検索したところ、以下の stack overflow の記事を見つけました。
TypeError: (0 , dayjs_1.default) is not a function
ここに書かれている Answer に従って、 date.ts
の 1 行目を以下のように書き換えます。
import dayjs from 'dayjs';export type DateTime = string;export const getCurrentDateTime = (): DateTime => { return dayjs().format('YYYY-MM-DD HH:mm:ss');};
import * as dayjs from 'dayjs';export type DateTime = string;export const getCurrentDateTime = (): DateTime => { return dayjs().format('YYYY-MM-DD HH:mm:ss');};
コードを書き換えた後で再度テストを実行すると問題なくテストが成功したのを確認できました。
テストの実行が長いなと思った時
先ほどの成功したテストの結果の「Time」をみると、1 つのテストを実行するのに 4.112 秒かかっています。
このテスト実行時間を短くしたい場合は、esbuild-jest
というのを使ってみるのも 1 つの手です。
esbuild-jest(GitHub レポジトリ)
esbuild-jest を使うための設定
基本的には「esbuild-jest(GitHub レポジトリ)」に書かれていることに従えば良いです。
まずは指示通りに以下のコマンドを実行して esbuild-jest
esbuild
をインストールします。
npm install --save-dev esbuild-jest esbuild
次も指示通りに、Jest の config ファイルを修正します。(5-7 行目のコードが、今回追加した部分)
export default { coverageProvider: 'v8', preset: 'ts-jest', testEnvironment: 'jsdom', transform: { '^.+\\.tsx?$': 'esbuild-jest', },};
インストール作業
とjest.config.tsの修正
が完了したら npm test
を実行してください。
そうすると以下のように再度dayjs
でエラーになるかと思います。
dayjs
の import 部分を、以下のように元の書き方に戻しておきます。(1 行目を元の書き方に戻す)
import dayjs from 'dayjs';export type DateTime = string;export const getCurrentDateTime = (): DateTime => { return dayjs().format('YYYY-MM-DD HH:mm:ss');};
元の書き方に戻した状態で再度 npm test
を実行すると以下のようにテストが成功するかと思います。
また、テストにかかっている時間もご覧ください。
esbuild-jest
を使わなかった時は 「4.112 秒」かかっていました。
しかし、esbuild-jest
を使った場合「1.765 秒」になりました。(約 2.3 倍の差)