Jest テストの記述
学習の目的
この単元を完了すると、次のことができるようになります。
- 各自の設定を検証するテストを記述する。
- 失敗するテストを記述して、合格するようコンポーネントを変更する。
- Jest の基本的なコマンドを挙げる。
- ライフサイクルフックについて説明する。
Lightning Web コンポーネントの事前準備
Lightning Web コンポーネントをテストするにはまず、テストするコンポーネントが必要です。
Lightning Web コンポーネントの作成
- Visual Studio Code で、Ctrl+Shift+P (Windows) または Cmd+Shift+P (macOS) を押して、コマンドパレットを開きます。
-
「lightning web」
と入力します。 - [SFDX: Create Lightning Web Component (SFDX: Lightning Web コンポーネントの作成)] を選択します。
- 新しいコンポーネントの名前に
「unitTest
」と入力します。 - Enter キーを押します。
- もう一度 Enter キーを押して、デフォルトの
force-app/main/default/lwc
を受け入れます。
lwc ディレクトリに unitTest ディレクトリが作成され、初期基本ファイルが設定されます。
基本的なテストの記述
Jest テストは、Aura コンポーネントの Lightning Testing Service に記述する Jasmine テストや Mocha テストとは、記述、保存、実行の方法が異なります。Jest テストはローカルに限定され、Salesforce とは関係なく保存され実行されます。実際、Jest テストを Salesforce 組織にリリースしようとすると、エラーが生じます。Lightning Web コンポーネントの Jest テストは Salesforce 組織にリリースされませんが、コンポーネント自体とともに必ずバージョン管理にコミットします。
__tests__ フォルダー
テストファイルはコンポーネントの他のファイルと切り離しておく必要があります。コンポーネントのバンドルフォルダーの最上位に __tests__ というフォルダーが自動作成されていなければ、作成します。このコンポーネントのテストはすべてこの __tests__ フォルダー内に保存します。
- Visual Studio Code で、
unitTest
ディレクトリを右クリックし、[New Folder (新しいフォルダー)] を選択します。 -
「__tests__」
と入力します。 - Enter キーを押します。
.forceignore の設定
__tests__ フォルダーをバージョン管理にコミットして、テストを他のチームメンバーやシステムと共有します。テストはプロジェクトや継続的インテグレーションプロセスの重要な要素です。Salesforce にリリースされないようにするために、.forceignore ファイルにテストの除外を入力しておきます。
- プロジェクトの
.forceignore
ファイルに次の除外が挿入されていることを確認します。挿入されていない場合は、追加してファイルを保存します。# LWC configuration files **/jsconfig.json **/.eslintrc.json # LWC Jest **/__tests__/**
Jest テストファイルを作成する
最初のテストは簡単です。引数として渡される 2 つの数字を加算することが期待される sum()
関数があります。
- Visual Studio Code で、
__tests__
ディレクトリを右クリックし、[New File (新しいファイル)] を選択します。 -
「sum.test.js」
と入力します。 - Enter キーを押します。
- 新しいテストファイルに次のコードを入力します。
import { sum } from '../sum'; describe('sum()', () => { it('should add 1 and 2 returning 3', () => { expect(sum(1, 2)).toBe(3); }); });
- ファイルを保存します。
テストの実行
- Visual Studio Code で、[View (表示)] を選択し、[Terminal (ターミナル)] を選択します。Visual Studio Code にターミナルが開きます。ターミナルはデフォルトで現在のプロジェクトの最上位ディレクトリに設定されます。
- ターミナルで、前の単元の次のコマンドを実行します。
npm run test:unit
- このテストは sum 関数がないため失敗します。
この修正方法を見てみましょう。
- Visual Studio Code で、
unitTest
ディレクトリを右クリックし、[New File (新しいファイル)] を選択します。 -
「sum.js」
と入力します。 - Enter キーを押します。
- 新しいファイルに次のコードブロックを入力します。
export function sum(x, y) { return x + y; }
- ファイルを保存します。
- ターミナルでもう一度テストを実行します。
npm run test:unit
- このテストは合格します。
お疲れさまでした。Jest が設定され、機能していることを確認できました。
何が行われたのかをテストコードで確認しましょう。
import { sum } from '../sum'; describe('sum()', () => { it('should add 1 and 2 returning 3', () => { expect(sum(1, 2)).toBe(3); }); });
- 1 行目で、sum JavaScript ファイルからエクスポートされた
sum
関数をインポートします。 - 3 行目以降が Jest テストスイートです。
describe
関数またはブロックがテストスイートで、2 つの引数を受け入れます。1 つ目はテストする単体の説明で、通常は名詞形です。2 つ目は 1 つ以上のテストを保持するコールバック関数です。明確にするために、describe
テストスイートを相互にネストすることも可能です。 - 4 行目がテストです (
it
はtest
の別名です)。このit
関数またはブロックも 2 つの引数を受け入れます。1 つ目は、期待する内容の説明で、通常は動詞で始まります。次は、テストを構築して、テストのアサーションまたは期待を保持するコールバック関数です。 - 5 行目は
expect
ステートメントで、sum
関数が 1 と 2 の 2 つの引数を追加して、3 を返すという成功条件をアサートしています。toBe
は多様な Jest マッチャーの 1 つです。
5 行目のすぐ後に、次の行のアサーションをもう 1 つ追加します。expect(sum(1, 2)).not.toBeGreaterThan(3);
-
.not
と.toBeGreaterThan
を追加することで、数値が 3 以下になります。あるいは、.not.toBeLessThan(3)
を使用した expect ステートメントを追加することも可能です。
ここからは Lightning Web コンポーネントのテストについてです。
Lightning Web コンポーネントの Jest テストでは、外部のコンポーネントやサービスとの連動関係を最小限に抑え、1 つのコンポーネントの動作を切り離してテストする必要があります。
プロセスを繰り返して unitTest
テストファイルを作成する
このテストでは、プロパティが設定されており、DOM に追加すると正しいテキストが表示されることを検証します。[SFDX: Create Lightning Web Component (SFDX: Lightning Web コンポーネントの作成)] コマンドの実行時に __tests__
フォルダー内に unitTest.test.js
が作成されています。
-
unitTest.test.js
内のコードを次のコードに置き換えます。import { createElement } from 'lwc'; import UnitTest from 'c/unitTest'; describe('c-unit-test', () => { afterEach(() => { // The jsdom instance is shared across test cases in a single file so reset the DOM while(document.body.firstChild) { document.body.removeChild(document.body.firstChild); } }); it('displays unit status with default unitNumber', () => { const element = createElement('c-unit-test', { is: UnitTest }); expect(element.unitNumber).toBe(5); // Add the element to the jsdom instance document.body.appendChild(element); // Verify displayed greeting const div = element.shadowRoot.querySelector('div'); expect(div.textContent).toBe('Unit 5 alive!'); }); });
- ファイルを保存します。
- ターミナルでもう一度テストを実行します。
npm run test:unit
- テストに失敗し、次の出力が表示されます。
Test Suites: 1 failed, 1 passed, 2 total Tests: 1 failed, 1 passed, 2 total
テストに合格するようコードを更新する前に、テストコードで何が必要なのかを確認しましょう。
- 1 行目は新しい内容です。
lwc
フレームワークからcreateElement
メソッドをインポートします。このメソッドはテストのみで使用できます。 - 2 行目はコンポーネントの JavaScript コントローラーから
UnitTest
クラスをインポートします。 - 4 行目以降が
describe
テストスイートブロックです。 - 5 行目は Jest のクリーンアップメソッドです。
afterEach()
は Jest の設定とクリーンアップのメソッドの 1 つです。各テスト後にそれ自体が存在する describe ブロックでafterEach()
が実行されます。このafterEach()
メソッドは、テストの最後に DOM をリセットします。テストの実行時、Jest がブラウザーを実行するわけではありません。Jest は jsdom を使用して、ブラウザーの DOM または document のように動作する環境を用意します。各テストファイルが jsdom のインスタンスを 1 つ取得しますが、テストの前後にファイル内の変更がリセットされるわけではありません。テストの出力が他のテストに影響しないように、テストの前後にクリーンアップすることがベストプラクティスです。設定とクリーンアップの他のメソッドも使用できます。「リソース」を参照してください。 - 12 行目からは
it
テストブロックです。 - 13 行目で、インポートした
createElement
メソッドを使用します。このメソッドはコンポーネントのインスタンスを作成し、element
定数に割り当てます。 - 16 行目には
unitNumber
変数が 5 に設定されていることをアサートするexpect
があります。これは私たちがテストしている 1 つ目の要件で、最初にこのunitNumber
が 5 に設定されています。 - 18 行目は実際に
appendChild
メソッドを使用して、element
をdocument.body
の jsdom のバージョンに追加します。このコールによって Lightning Web コンポーネントが DOM にアタッチされ表示されます。つまり、connectedCallback() と renderedCallback() というライフサイクルフックがコールされるということです (詳細は後述)。 - 20 行目は
querySelector
(標準の DOM クエリメソッド) を使用してdiv
タグの DOM を検索します。element.shadowRoot
をクエリの親として使用します。これは、シャドウの境界を越えてコンポーネントのシャドウツリーを検査できる、テスト限定の API です。 - 最後の 21 行目は
div
タグのtextContent
を確認して、「Unit 5 alive!」であることをアサートするexpect
です。これが最後の要件です。テキストが正しいことをアサートしています。
テストに合格するには、unitTest の HTML ファイルと JavaScript ファイルにコードを追加する必要があります。要件を満たすコードを追加しましょう。
-
unitTest.html
ファイルをクリックして開きます。 -
unitTest.html
を次のコードで上書きします。<template> <lightning-card title="Unit Status" icon-name="standard:bot"> <div class="slds-m-around_medium"> Unit {unitNumber} alive! </div> </lightning-card> </template>
- ファイルを保存します。
-
unitTest.js
ファイルをクリックして開き、次のコードで上書きします。import { LightningElement, api } from 'lwc'; import { sum } from './sum'; export default class UnitTest extends LightningElement { @api unitNumber = sum(2,3); }
- ファイルを保存して、テストを実行します。
npm run test:unit
- すべてのテストに合格します。
DOM の非同期更新のテスト
Lightning Web コンポーネントの状態が変わると、DOM が非同期で更新されます。テストの更新が完了するまで待機してから結果が評価されるようにするには、解決された Promise を返します。具体的には、テストコードの残りの部分を、解決された Promise にチェーニングします。Promise チェーンが完了するまで待機して、Jest がテストを終了します。拒否状態で Promise が終了すると、Jest テストに失敗します。
-
unitTest.test.js
を開きます。 - 前回のテストの後にこの 2 つ目のテストを追加します。
このテストでは、プロパティの変更によって DOM のテキストが更新されることを検証します。it('displays unit status with updated unitNumber', () => { const element = createElement('c-unit-test', { is: UnitTest }); // Add the element to the jsdom instance document.body.appendChild(element); // Update unitNumber after element is appended element.unitNumber = 6 const div = element.shadowRoot.querySelector('div'); // Verify displayed unit status expect(div.textContent).toBe('Unit 6 alive!'); });
- ファイルを保存して、テストを実行します。
npm run test:unit
- 次の失敗メッセージが表示されます。
Expected: "Unit 6 alive!" Received: "Unit 5 alive!"
どの様な問題が起きていますか? expect
ステートメントが div.textContext
は「Unit 6 alive」であるべきとアサートしているのに、「Unit 5 alive!」のままになっています。この変更を確認するには、解決された Promise
を返して、変更されるまで待機する必要があります。
-
// Verify display unit status
の後にある、失敗の原因となったexpect
ステートメントを次のコードに置換します。expect(div.textContent).not.toBe('Unit 6 alive!'); // Return a promise to wait for any asynchronous DOM updates. Jest // will automatically wait for the Promise chain to complete before // ending the test and fail the test if the promise rejects. return Promise.resolve().then(() => { expect(div.textContent).toBe('Unit 6 alive!'); });
- 前回と同じコマンドを使用してテストを実行するか、前の単元の「Jest テストの実行」セクションに記載されている他のいずれかのオプションを使用します。
- このテストは合格します。
順調です。2 つのテストスイートで 3 つのテストに成功したことになります。次に 4 つ目のテスト、を追加して、入力項目が更新されたときに単体の状況が更新されることを確認できるようにします。この場合は、入力項目に変更イベントを使用します。
-
unitTest.test.js
がまだ開いていない場合は開きます。 - 追加した前回のテストの後に行を追加して、この 3 つ目のテストをスイートに追加します。
it('displays unit status with input change event', () => { const element = createElement('c-unit-test', { is: UnitTest }); document.body.appendChild(element); const div = element.shadowRoot.querySelector('div'); // Trigger unit status input change const inputElement = element.shadowRoot.querySelector('lightning-input'); inputElement.value = 7; inputElement.dispatchEvent(new CustomEvent('change')); return Promise.resolve().then(() => { expect(div.textContent).toBe('Unit 7 alive!'); }); });
- ファイルを保存してテストを実行し、失敗メッセージを確認します。
テストが 1 つだけ実行され、他の 2 つはスキップされたことがわかります。
テストされた内容を見てみましょう。
- 最初の数行は以前と同じです。UnitTest を
document.body
に追加して、div
への参照を作成しています。 -
inputElement
定数に lightning-input 項目への参照が設定されています。 - 続いて、その入力項目の値が 7 に設定されています。
- そして、
dispatchEvent
を使用して、「change」というイベント種別を設定したCustomEvent
でイベントをトリガーします。 -
Promise
は以前と同じで、変更された入力項目の値についてのみ変更されています。
では、合格できるようコードを更新しましょう。この場合は、lightning-input
を HTML ファイルに追加し、handleChange
メソッドを JavaScript コントローラーに追加します。
-
unitTest.html
を開きます。 - 次のコードを
lightning-card
内のdiv
の前に追加します。<lightning-input label="Unit Number" value={unitNumber} onchange={handleChange} > </lightning-input>
- ファイルを保存します。
-
unitTest.js
を開きます。 - 次のコードを
@api unitNumber
ステートメントの後に追加します。handleChange(event) { this.unitNumber = event.target.value; }
- ファイルを保存して、テストを実行します。
- 入力要素と JavaScript ハンドラーが追加されたため、テストに合格します。
ライフサイクルフックのテスト
Lightning Web コンポーネントには、フレームワークで管理されるライフサイクルがあります。このフレームワークは、コンポーネントを作成して DOM に追加または DOM から削除したり、コンポーネントの状態の変更時に DOM の更新を表示したりします。ライフサイクルを操作するためのメソッドがいくつか存在します。
connectedCallback()
ライフサイクルフックは、コンポーネントが DOM に挿入されたときに起動します。disconnectedCallback()
ライフサイクルフックは、コンポーネントが DOM から削除されたときに起動します。これらのフックの用途の 1 つは、イベントリスナーを登録または登録解除することです。
適切な例として、lwc-recipes サンプルリポジトリの lmsSubscriberWebComponent のコードを参照してください。
次は、ワイヤーサービスの Jest テストの記述について見ていきます。
リソース
- Salesforce ブログ: Unit Test Lightning Web Components with Jest (Jest を使用した Lightning Web コンポーネントの単体テスト)
- 開発者ガイド: Lightning Web コンポーネント用の Jest テストの記述
- 開発者ガイド: Lightning Web コンポーネントライフサイクル
- 外部サイト: Jest: Getting Started (使用開始)
- 外部サイト: Jest: Expect Reference (expect のリファレンス)
- 外部サイト: Jest: Setup and Teardown (設定と分解)
- 外部サイト: GitHub: salesforce/sfdx-lwc-jest
- 外部サイト: GitHub: trailheadapps/lwc-recipes
- 外部サイト: Wiki: Test-Driven Development (テスト主導型開発)