Jestの基本
create-react-appでJestを用いたテストコードを作成する場合は、次の2つのルールのどちらかを守る必要があります。
-
src/__tests__
フォルダの中にテストコードを配置する(ファイル名は何でもよい) -
ファイル名の末尾を
.test.js
にする(src
内ならどこに置いてもよい)
今回は後者のルールを中心にファイルを作成していきます。まずは、Jestの使い方の基本を見ていきましょう。
最小の例
前述のsum.jsのテストコードとして、sum.test.jsを作成してみましょう(リスト2)。
import { sum } from "./sum"; // (1) test("1足す1は2になる", () => { // (2) // 条件 const a = 1; const b = 1; // テスト対象の処理を実行する const actual = sum(a, b); // (3) // 処理結果が期待したものになっているかを検証する expect(actual).toBe(2); // (4) });
まずは通常のコードから利用する時と同様に、テスト対象の処理をimportします(1)。次に、グローバルスコープに定義されているtest
関数を使って、テストの名前とテスト内容の関数を定義します(2)。なお、test
関数により定義された、試行1回分のテストを「テストケース」と呼びます。テストケースの中では、テスト対象の処理を任意のパラメータで実行します(3)。最後に、グローバルスコープに定義されているexpect
関数を使って、処理結果が期待通りかを検証します。これらのグローバル関数は、テストの実行時にJestが提供しているものです。
テストコードを作成したら、ターミナルでリスト3のコマンドを実行します。
$ npm run test
実行するとターミナルが図1のような状態になります。
テストケースの名前である「1足す1は2になる」が表示されて、テストが成功したことが示されています。これがJestを用いたテストの最小の形です。
create-react-appでは監視モードでJestが起動されるので、テストファイルを更新するたびに、テストが再実行されます。テストを終了したい場合はキーボードで「q」を押してください。テストファイルがたくさんある場合には、直近で編集したファイルのみがテスト対象になりますが、「a」を押すことでsrc
フォルダ内のすべてのテストファイルを実行できます。監視モードで実行したくない場合にはCI=true npm run test
のように先頭にCI=true
を付けることで、一通りのテストが終わったら終了する形式で実行できます。
また、テストファイルの中にテストケースを複数記述することで、さまざまなパターンのテストを行うこともできます(リスト4)。
import { sum } from "./sum"; test("1足す1は2になる", () => { ... }); test("1足す2は3になる", () => { ... }); test("4足す5は9になる", () => { ... }); test("1足す-1は0になる", () => { ... });
この仕組みを用いることで、大量のテスト≒動作確認を高速に実施することができます。テスト対象のコードの種類によっては、先にテストケースを用意して動作確認を済ませてから、アプリに組み込んだほうが楽に開発を進められる場合もあります。
複数の関数のテストを分かりやすく管理する
ファイル名の命名の特性上、JSファイル(XXX.js)1つに対して同名のテストファイル(XXX.test.js)を1つ作成する場合が多くなりますが、1つのJSファイルには複数の関数が含まれていることもしばしばあります。テストケースに「○○関数にaとbを渡したときにcになること」や「△△関数にxとyを渡したときにzになること」などの名前を付けながら管理することも可能ですが、少し冗長ですね。
Jestでは、テストケースをグループ分けして管理するための仕組みが用意されています。足し算だけではなく、引き算(sub)も扱えるcalc.jsを作成して、そのテストファイルがどうなるのかを見てみましょう(リスト5)。
// src/calc.test.js import { sum, sub } from "./calc"; describe("sum(a, b)", () => { // (1) test("1足す1は2になる", () => { const actual = sum(1, 1); expect(actual).toBe(2); }); test("2足す2は4になる", () => { const actual = sum(2, 2); expect(actual).toBe(4); }); }); describe("sub(a, b)", () => { test("1引く1は0になる", () => { const actual = sub(1, 1); expect(actual).toBe(0); }); test("5引く2は3になる", () => { const actual = sub(5, 2); expect(actual).toBe(3); }); });
グループ分けして管理する場合、グローバルスコープに定義されているdescribe
関数を使用します。使い方はtest
関数とほとんど同じ(1)ですが、describe
関数の中には複数のテストケースを定義する点が異なります。describe
関数を用いて記述したテストファイルを実行すると、テスト結果が図2のような見た目に変わります。
何の関数をテストした結果なのか一目で分かりますね。本来はdescribe
関数の第一引数に付ける名前は何でも構いませんが、筆者は好んで関数の名前を付けています。他にも管理しやすい命名のし方があると思うので、工夫してみてください。
expectのよく使う機能
Jestでは、実行結果を検証するためにexpect
関数を用います。これまでの例ではtoBe
関数をつなげて使用することで値を評価していましたが、他にも多くの評価用関数が用意されています。全体としてはこちらのドキュメントで紹介されています。
本記事では、頻繁に使うもののみをリスト6に挙げます。
// 基本型の値の同一性を評価するtoBe expect(123).toBe(123); // オブジェクトの値の同一性を再帰的に評価するtoEqual expect({ a: 1, b: 2 }).toEqual({ a: 1, b: 2 }); // 条件に一致しないことを確認するnot expect(123).not.toBe(0); expect({ a: 1, b: 2 }).not.toEqual({ c: 1 }); // エラーが発生することを確認するtoThrow expect(() => { sum("one", "two"); // 数値ではないのでエラーが出る }).toThrow();
これらの組み合わせで多くのテストは実施できますが、さらにかゆいところに手が届く便利な機能も用意されているので、困った時にはドキュメントを眺めてみてください。