2020.09.28

JestでLightning Web Componentのテストコードを実装する方法

こんにちは。黄 鷹(コウ オウ)です。
今回はJestを使ってLightning Web Component(以降lwc)のテストコードを実装する方法について紹介します。

Jest とは

Jest は javascript のテストフレームワークです。
lwc以外、Node.js、Angular.js などをテストすることができます。

テスト結果にコードカバレッジを含めることが可能です。
テストする際、サーバー通信は発生しないため、サーバー通信結果のモックを作る必要があります。

環境準備

Jestの導入するには下記が必要です:

  1. Node.js と npm インストール(オフィシャル) インストール(日本語)
  2. Salesforce CLI (以降:sfdx-cli)オフィシャルサイト

1 と 2 は別々でインストールすることが可能ですが、npmなどのパッケージ管理ソフトを使ってインストールすることをおすすめします。

npm で sfdx-cli をインストールする場合、npm インストール後にnpm install -g sfdx-cliを実行します。

sfdx-cliインストール後、jestのインストールに移ります。

Jestをインストール及び設定

Jestはプロジェクト単位でインストールするため、事前にローカルに下記を用意します。

コマンドラインで現在のディレクトリを前述Salesforceプロジェクトのルートディレクトリにした後、下記順番のコマンドを実行してJestをインストールします。

  1. npm init -y
    • Salesforceプロジェクトにnpmの初期化を行います。正常終了の場合、プロジェクトのルートディレクトリにpackage.jsonが作成されます。
    • -yを外して実行した場合、package.json作成時に一部項目の入力が要求されます 。
  2. sfdx force:lightning:lwc:test:setup
    • Jest及び関連モジュールをインストールします。

Jestインストール完了後、package.jsonを開いて、scriptsブロックが空白の場合、下記のように追記します(...除く)。

{
  ...
  "scripts": {
    ...
    "test": "npm run test:unit",
    "test:unit": "sfdx-lwc-jest",
    "test:unit:watch": "sfdx-lwc-jest --watch",
    "test:unit:debug": "sfdx-lwc-jest --debug",
    "test:unit:coverage": "sfdx-lwc-jest --coverage",
    ...
  },
  ...
}
package.json

テスト対象 lwc 作成

サンプルとして、下記取引先と取引先責任者を出力する lwc を用意します。

<template>
   <div class="content">
       <template if:true={accountRecord}>
           <div class="name">
               <div>取引先名:</div>
               <div>{name}</div>
           </div>
       </template>
   </div>

   <lightning-card title="取引先責任者" icon-name="custom:custom107">
       <template if:true={contacts}>
           <template for:each={contacts} for:item="contact">
               <p key={contact.Id}>{contact.LastName} {contact.FirstName}</p>
           </template>
       </template>
  </lightning-card>
</template>
myAccountLwcComp.html
import { LightningElement, wire, api, track } from 'lwc';

import { getRecord } from 'lightning/uiRecordApi';
import getContacts from '@salesforce/apex/MyAccountLwcCompController.getContacts';

import ACCOUNT_NAME from '@salesforce/schema/Account.Name';

export default class MyAccountLwcComp extends LightningElement {
    // 取引先Id
    @api recordId;
    // 取引先レコード
    accountRecord;
    // 取引先責任者
    contacts;

    // 取引先名
    name = '';

    @wire(getRecord, { recordId: '$recordId', fields:[ACCOUNT_NAME] })
    wiredRecord({ data }) {
        if (data) {
            this.accountRecord = data;
            this.name = data.fields.Name.value;
        }
    }

    @wire(getContacts, { accountId: '$recordId' })
    wiredContacts({ data }) {
        if (data) {
            this.contacts = data;
        }
    }
}
myAccountLwcComp.js
public with sharing class MyLwcComp1Controller {

    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts(Id accountId){
        return [
            SELECT Id, Name
            FROM Contact
            WHERE AccountId = :accountId
        ];
    }
}
MyAccountLwcCompController.cls

テストコード作成

Jestのテストコードを作成するにはまず、下記コマンドを実行してテスト用のjavascriptファイルを作成します。

sfdx force:lightning:lwc:test:create -f force-app/main/default/lwc/myButton/myAccountLwcComp.js

-fのパスはテストコードのパスではなく、テスト対象lwcのjavascriptファイルのパスであることに注意してください。

コマンド実行後、下記のものが作成されます:

  • lwcバンドルのフォルダ内に__tests__フォルダが作成されます。
    • 今回の例だと:force-app/main/default/lwc/myAccountLwcComp/__tests__になります。
  • __tests__フォルダ内に myAccountLwcComp.test.js ファイル作成されます。

sfdx-cliは自動的にやってくれますが、念の為プロジェクトのルートにある.forceignoreファイルを開いて、__tests__が除外対象であることを確認します。具体的には下記のような構文です:

# LWC Jest
**/__tests__/**
.forceignore

上記の記述で__tests__フォルダを force:source:status のチェック対象から外すことができます。Scratch Orgを使わない場合は気にする必要がありません。

myAccountLwcComp.test.js を開いて、下記のようにテストコードを入れます。

import { createElement } from 'lwc';
import MyAccountLwcComp from 'c/myAccountLwcComp'; // テスト対象lwcインポート

// ワイヤーアダプターインポート
// 標準Lightning Data Service
import { registerLdsTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
// Apex クラス
import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';

// lwcに使われたワイヤーサービスをインポート
import { getRecord } from 'lightning/uiRecordApi';
import getContacts from '@salesforce/apex/MyAccountLwcCompController.getContacts';

// テスト用ワイヤーアダプター登録
const getRecordWireAdapter = registerLdsTestWireAdapter(getRecord);
const getContactsWireAdapter = registerApexTestWireAdapter(getContacts);

// ワイヤーアダプターのテスト用レスポンスをインポート
const mockGetRecord = require('./data/getRecord.json');
const mockGetContacts = require('./data/getContacts.json');

// describeはテストスイートに相当します
// describe内複数のit(), test() をぶら下がることが可能
// 1番目の引数はテストスイート名で、コマンドでファイル作成する場合
// 自動的に設定されます。
describe('c-my-account-lwc-comp', () => {
    afterEach(() => {
        // afterEachはdescribeブロック内
        // 各it()、もしくはtest()が実行完了した後に実行されます
        while (document.body.firstChild) {
            // it()、test() 開始時に作成したlwcのDOMをクリアする
            document.body.removeChild(document.body.firstChild);
        }
    });

    // it(), test() はテストケースに相当します。it は test の略称なので
    // it() を使うか test() を使うかは開発者の好みでいいです。
    // なお、コマンドでファイル作成する場合、デフォルトはit()
    // 1番目の引数はテストケース概要で、テスト目的がわかる情報を記入します。
    it('testGetRecord', () => {
        // lwcのDOMを作成する
        const element = createElement('c-my-account-lwc-comp', {
            is: MyAccountLwcComp
        });
        document.body.appendChild(element); //lwcのDOMをdocumentに入れる

        //@wire のダミーサーバー通信結果生成
        getRecordWireAdapter.emit(mockGetRecord);

        return Promise.resolve().then(() => {
            const content = element.shadowRoot.querySelector('.content');
            const nameField = mockGetRecord.fields.Name.value;

            //テンプレートリレラル(` backtick)
            expect(content.textContent).toBe(`:${nameField}`)
        });
    });

    test('testGetContacts', () => {
        const element = createElement('c-my-account-lwc-comp', {
            is: MyAccountLwcComp
        });
        document.body.appendChild(element);

        //@wire のダミーサーバー通信結果生成
        getContactsWireAdapter.emit(mockGetContacts);

        return Promise.resolve().then(() => {
            const contactElements = element.shadowRoot.querySelectorAll('p');
            expect(contactElements.length).toBe(
                mockGetContacts.length
            );
        });
    });
});
myAccountLwcComp.test.js

テスト対象コードに @wired サービスも使用していますので、それもテスト対象になります。

Jestのテストは実際サーバー通信発生しないため、サーバー通信が発生する処理をテストする際、LDS、Apexメソッドからのレスポンスは別途用意する必要があります。

まず、__tests__フォルダ内にdataフォルダを作成し、下記.jsonファイルを作成します。

  • getRecord.json
  • getContacts.json

各ファイルの中身は下記の通りです。

{
   "fields": {
       "Name": {
           "value": "デモ取引先"
       }
   }
}
getRecord.json
[
  {
    "Id": "1",
    "LastName": "test",
    "FirstName": "contact A1"
  },
  {
    "Id": "2",
    "LastName": "test",
    "FirstName": "contact A2"
  },
  {
    "Id": "3",
    "LastName": "test",
    "FirstName": "contact A3"
  }
]
getContacts.json

テスト実施

Jestのテストを実施するには、コマンドラインに現在のディレクトリをプロジェクトのルートディレクトリに移動し、下記コマンドを実行します。

npm run test:unit

コードの変更を保存する度にテストを実施するには下記コマンドを実行します。

npm run test:unit:watch

デバッグモードでテストを実行するには下記コマンドを使います。

npm run test:unit:debug

テストを実行し、終わったらカバー率を出力するには下記コマンドを使います。

npm run test:unit:coverage

上記コマンドは全部 package.json の scripts ブロックに定義したものです。 そのまま実行するとプロジェクト内すべてのテストコードが実行されます。

個別テストを実行したい場合は、下記のようにコマンドの後に.test.jsのファイル名を追加しましょう。

npm run test:unit myAccountLwcComp.test.js

テスト結果確認

正常終了

npm run test:unit の実行結果
  • Test Suites
    • テストスイート(describeに該当します)
  • Tests
    • テストケース( it()、test() に該当します)
  • Snapshots
    • UI(スナップショット)テスト。
  • Time
    • テスト実行時間(秒)

コードカバー率

npm run test:unit:coverage の実行結果です。 npm run test:unit の結果に加えて、テスト対象のカバレッジ情報も出力されます。

カバレッジテーブル部分のヘッダーについて:

  • File
    • テスト対象 javascript ファイル
  • Stmts
    • 実行された命令のカバレッジ
  • Branch
    • if、caseなど分岐のカバレッジ
  • Funcs
    • 関数呼び出しのカバレッジ
  • Lines
    • テスト対象ファイル内実行可能の行のカバレッジ
  • Uncovered Line
    • テストでカバーできなかった行

コマンドラインの出力はもちろん、他にはプロジェクトのルートディレクトリにcoverageのフォルダが自動的に作成されます。coverage/lcov-report/index.htmlを実行すると下記のようなウェブページ版テスト結果が見られます。

assert 失敗

assert 失敗時の結果です。assert情報とエラーのスタックトレースが出力されます。
  • Expected
    • 期待結果
  • Received
    • 実際結果

後書き

ここまでご覧いただきありがとうございます。

lwcのテストは Apex のテストクラスのように強制ではないので、なくてもリリースに支障はありません。

ただし、テストコードがあれば、下記のようなメリットがあります:

  • CI/CDパイプラインで自動テストすることが可能です。
  • 画面操作しなくてもテスト実施でき、しかも速いので、テスト駆動開発・コードメンテナンスには最適です。
  • テストコードは Selenium Webdriver のUIテストにも使えます。

最終的にlwcの品質向上につながるので、初期設定は諸々ありますが、開発・テスト時に楽にするためにもぜひ習得したいものです。

参考資料

39 件
     
  • banner
  • banner

関連する記事