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();
これらの組み合わせで多くのテストは実施できますが、さらにかゆいところに手が届く便利な機能も用意されているので、困った時にはドキュメントを眺めてみてください。
