長らく停滞していたFacebook製のJavaScriptテストフレーム「Jest」が息を吹き返しています。概要とサンプルを使ったテストの手順を紹介します。JavaScript PlaygroundのJack Franklinによる特別寄稿です。
この記事ではFacebookが開発したテストフレームワークJestを使い、ReactJSコンポーネントをテストする方法を説明します。Jestの独創的な機能、特にReactアプリ向けテストを簡単に実施する機能について説明するまえに、プレーンなJavaScript関数でのJestの使用法を紹介します。
Jestが注目に値するのは、Reactに対応しているだけでなく、JavaScriptアプリケーションのテストにも使用できるからです。ただし、いくつかの機能はユーザーインターフェイスのテストにとても役立つ形で提供されるので、Reactにぴったりです。
サンプルアプリケーション
テスト用のアプリケーションが必要です。従来のWeb開発のルールに則って、小規模なToDoアプリケーションからスタートしましょう。GitHubに掲載していますので、参照してください。アプリケーションの使用感を試したいなら、オンラインライブデモもあります。
アプリケーションはES2015で記述され、Babel、ES2015、Reactのプリセットを使用したWebpackを使ってコンパイルされます。ビルド設定の詳細には触れませんが、確認したい場合はGitHubのリポジトリを見てください。アプリケーションをローカルで実行する手順はREADMEにあります。Webpackを使ったアプリケーション構築については「WebPACKのビギナーズガイド」をおすすめします。
アプリケーションのエントリーポイントapp/index.jsは、Todosコンポーネントを次のようにHTMLにレンダリングします。
<title>What is Search Engine Marketing?</title>
<title>How to Make the Perfect Pie Crust</title>
render(
<Todos />,
document.getElementById('app')
);
Todosコンポーネントはアプリケーションのメインハブです。すべてのステートが含まれており(このアプリケーションの場合はハードコーディングしたデータですが、現実にはAPIまたはそれに類似するものから受け取る可能性が高い)、2つの子コンポーネントをレンダリングするコードがあります。それぞれのtodoのステートごとにレンダリングされるのがTodoで、一度レンダリングされ、新しいtodoを追加するためのフォームをユーザーに提供するのがAddTodoです。
Todosコンポーネントにはすべてのステートが含まれているので、変更のたびに通知をするためにTodoおよびAddTodoコンポーネントを必要とします。従って、データの変更があったときに呼び出せるコンポーネントに関数を渡し、それに応じてTodosが状態を更新します。
これで差し当たり、すべてのビジネスロジックがapp/state-functions.jsに含まれていることが分かります。
export function toggleDone(state, id) {...}
export function addTodo(state, todo) {...}
export function deleteTodo(state, id) {...}
これらはすべて、ステートや一部のデータを受け取り、新しい状態を返す純粋関数です。純粋関数とは、与えられたデータのみを参照し副作用がない関数です。詳しくは、私がA List Apartで純粋関数について書いた記事と、『Reactアプリのコードを美しく保つ「高階コンポーネント」の考え方とは?』を読んでください。
これらは、Reduxでレデューサーと呼ばれるものにかなり似ています。実際に、このアプリケーションがかなり大きくなった場合は、データをより明示的にして構造的にアプローチするためにReduxへの移行を検討します。しかし、このサイズのアプリケーションなら、多くの場合はローカルコンポーネントのステートとうまく抽象化された関数で十分だと分かります。
TDDをするか、しないか
テスト駆動開発(TDD)の賛否についてたくさんの記事が書かれており、開発者は修正コードを書く前にまずテストを書くことが求められています。この背後にある考え方として、テストを書くことでいま書いているAPIについてよく考えることになり、よりよい設計につながる可能性があります。
Reactコンポーネントの場合、コンポーネントを記述し、そのあとにもっとも重要な機能にテストを追加します。ただし、コンポーネントにテストを記述する方がワークフローに沿うなら、そうしてください。堅苦しいルールはありません。チームのために最善だと思うことをしてください。
Jestの紹介
Jestがリリースされたのは2014年で、最初は多くの関心を集めましたが、プロジェクトはしばらく休止状態となり積極的な取り組みはされませんでした。しかし、Facebookは昨年Jestの改善に投資し、最近になって再考の価値がある印象的な変化をもたらすリリースを発表しました。最初にリリースされたオープンソースのJestと比べると、類似点は名称とロゴだけで、そのほかはすべて変更や書き直しがされました。もっと詳しく知りたければ、プロジェクトの現在の状態を語っているChristoph Pojerのコメントを読んでください。
ほかのフレームワークを使用したBabel、React、JSXのテスト設定に手こずっているなら、Jestを強くおすすめします。既存のテスト設定が遅くなった場合にも、Jestがとても有効です。テストを自動的に並行して実行し、ウォッチモードは変更されたファイルに関連するテストのみを実行できるので、テスト範囲が広いときにとても重宝します。JSDom設定を備えていて、ブラウザーテストを記述できます。Nodeを介して実行され、非同期テストに取り組め、組み込みのモック、スパイ、スタブなど高度な機能があります。
Jestのインストールと設定
はじめにJestをインストールします。Babelも使用中なので、JestとBabelが難しい設定なしでうまく動作するよう次のようなモジュールをインストールします。
npm install --save-dev babel-jest babel-polyfill babel-preset-es2015 babel-preset-react jest
また、必要なプリセットやプラグインを使用するためにBabelに.babelrcファイルを設定します。このファイルを使ったサンプルは次のようになります。
{
"presets": ["es2015", "react"]
}
コンポーネントを使わずステート関数を使ってテストを開始するので、Reactテストツールはまだインストールしません。
Jestは__tests__フォルダーにテストがあることを求めます。これはJavaScriptコミュニティで評判の良い慣例で、ここでも忠実です。__tests__セットアップを使いたくない場合は、難しい設定が一切ないJestが.test.jsファイルと.spec.jsファイルの検索もサポートします。
ステート関数をテストするには、次へ進み__tests__/state-functions.test.jsを作成します。
あとですぐに適切なテストを書きますが、いまのところはこのダミーテストを設置します。これですべて正常に動作しているか、そしてJestを設定できているか確認できます。
describe('Addition', () => {
it('knows that 2 and 2 make 4', () => {
expect(2 + 2).toBe(4);
});
});
では、package.jsonへ進みます。Jestを実行できるようにnpm testを設定し、jestを実行するためにtestを設定してシンプルにします。
"scripts": {
"test": "jest"
}
npm testをローカルで実行しているなら、テストを実行すればパスするはずです。
PASS __tests__/state-functions.test.js
Addition
✓ knows that 2 and 2 make 4 (5ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 passed, 0 total
Time: 3.11s
Jasmine、あるいはほとんどのテストフレームを使ったことがあるなら、上のテストコードに見覚えがあるはずです。Jestはdescribeを使い、必要に応じてitをテストのネストに使用します。どのくらいネストを使用するかは自由です。私はネストをしたいのですべての説明の文字列をdescribeに渡し、itがセンテンスとして読み取ります。
実際のアサーション作成に関して言えば、テストしたいものをexpect()コール内にラップしてからアサーションに呼び出します。今回のケースではtoBeを使用しています。利用可能なアサーションの一覧はJestのドキュメントにあります。toBeは与えられた値がテスト下の値と適合するか、===を使って確認します。この記事にもJestのアサーションがいくつかあります。
ビジネスロジックをテストする
Jestがダミーテストで動作すると分かったので、実際の環境で動かしてみます。まずステート関数toggleDoneでテストします。toggleDoneはトグルしたい現在の状態とtodoのIDを受け取ります。各todoにはdoneプロパティがあり、toggleDoneはtrueからfalseへ、あるいはその逆にdoneプロパティをラップします。
これに従っている場合は、リポジトリをクローンし、___tests__フォルダーが含まれている同じディレクトリにappフォルダーをコピーしているか確認してください。Todoアプリの依存オブジェクトshortidパッケージ(npm install shortid --save)もインストールします。
app/state-functions.jsから関数をインポートし、構造を設定することから始めます。Jestでは必要なだけ深くネストできるdescribeやitが使える上に、読み込みがうまくいくことの多いtestも使用できます。testはJestのit関数の別名ですが、テストをより読み込みやすくし、ネストを少なくできます。
例としてdescribeとitコールをネストする記述方法は次のとおりです。
import { toggleDone } from '../app/state-functions';
describe('toggleDone', () => {
describe('when given an incomplete todo', () => {
it('marks the todo as completed', () => {
});
});
});
testを使って記述する方法は次のとおりです。
import { toggleDone } from '../app/state-functions';
test('toggleDone completes an incomplete todo', () => {
});
テストはうまく読み込みますが、これはインデントの少ない方法です。インデントの多い、少ないは個人的な好み次第です。使いやすい方を選んでください。
これでアサーションを記述できます。まずtoggleDoneに渡す前に、トグルしたいtodoのIDとともに開始状態を作成します。toggleDoneは最終状態を返すので、そのあと次のようにアサートします。
const startState = {
todos: [{ id: 1, done: false, name: 'Buy Milk' }]
};
const finState = toggleDone(startState, 1);
expect(finState.todos).toEqual([
{ id: 1, done: true, name: 'Buy Milk' }
]);
アサーションの作成にtoEqualを使用していることに注意してください。オブジェクトや配列のtoEqualではなく、文字列や数字などのプリミティブ値でtoBeを使用してください。toEqualは配列やオブジェクトを扱うために構築され、与えられたオブジェクト内で各フィールドやアイテムが一致するか再帰的に確認します。
これでnpm testを実行し、状態関数がテストをパスします。
PASS __tests__/state-functions.test.js
✓ tooggleDone completes an incomplete todo (9ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 passed, 0 total
Time: 3.166s
変更の際の再実行テスト
テストファイルに変更を加えるのは少しわずらわしく、npm testを手動でもう一度実行しなければなりません。ウォッチモードは、ファイルの変更を監視し、テストを実行するJestの最高機能の1つです。変更されたファイルに基づいて、テストのどのサブセットが実行されるか把握します。これは信じられないほど強力で信頼性が高く、ウォッチモードでJestを実行したりコードを作りながら一日中放置できます。
ウォッチモードで実行するにはnpm test ----watchを実行します。最初の--のあとでnpm testに渡すものは基礎となるコマンドへすべて直接渡されます。これは2つのコマンドが事実上同等であることを意味します。
- npm test ----watch
- jest --watch
この後の記事では、Jestを別のタブあるいはターミナルウィンドウで実行したままにすることをお勧めします。
Reactコンポーネントのテストをはじめる前に、ステート関数にもう1つテストを書きます。実際のアプリケーションではテストをたくさん書きますが、チュートリアルでは一部省略します。差し当たりはdeleteTodo関数の動作を保証するテストを書きます。まず自分で書いてみてから以下に紹介する記述方法を見て比較してください。
テストは最初とほぼ同じで、初期状態を設定し、関数を実行し、そのあと最終状態でアサートします。Jestをウォッチモードで実行したままにする場合は、どうやって新しいテストをピックアップし実行するか、そしてどのくらい速いかに注目してください。これはテストを書いてすぐにフィードバックを得るための素晴らしい方法です。
また、上記のテストは完璧なレイアウトを実証しています。
- 設定
- テスト下での関数の実行
- 結果のアサート
常にテストをこのように設計すると、同じように実施するのが簡単になります。
これでステート関数のテストに満足したので、Reactコンポーネントへ進みます。
※本記事は、ゲストライターのJack Franklinによるものです。SitePointのゲスト投稿では、JavaScriptコミュニティの著名な執筆者や講演者の魅力的なコンテンツの提供を目指しています。本記事はDan Prince、Christoph Pojerが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者のみなさんに感謝します。
(原文:How to Test React Components Using Jest)
[翻訳:柴田理恵/編集:Livit]