Skip to main content

ワイヤーサービスの Jest テストの記述

学習の目的

この単元を完了すると、次のことができるようになります。

  • ワイヤーサービスの主なアダプターを 3 つ挙げる。
  • ワイヤーサービスのデータのモックについて説明する。
  • リアクティブ変数とその影響を理解する。

@wire サービスのテスト

Lightning Web コンポーネントは、Lightning データサービス上に構築されたリアクティブワイヤーサービスを使用して、Salesforce データを読み取ります。コンポーネントは JavaScript クラスの @wire を使用して、lightning/ui*Api モジュールのいずれかのワイヤーアダプターからデータを読み取ります。

このワイヤーサービスがリアクティブである理由の 1 つは、リアクティブ変数をサポートしていることです。リアクティブ変数には $ のプレフィックスが付いています。リアクティブ変数が変更されると、ワイヤーサービスが新しいデータをプロビジョニングします。データがクライアントのキャッシュに存在する場合は、ネットワークリクエストを伴わないことがあります。

ここでは、@salesforce/sfdx-lwc-jest テストユーティリティを使用して、これらのコンポーネントがワイヤーサービスからのデータやエラーをどのように処理するかをテストします。

テストをする場合、テストで使用する入力を完全に制御できることが求められます。外部のコードやデータとの連動関係は認められません。ここでは、sfdx-lwc-jest からデータをモックするテストユーティリティ API をインポートして、テストがリモート呼び出しやサーバー遅延といった予測不能な要因に左右されないようにします。

ワイヤーサービスのデータのモックに使用するアダプターには次の 3 種類があります。

  • 汎用ワイヤーアダプター: emit() API をコールすると、汎用アダプターがデータをオンデマンドで生成します。データ自体に関する追加情報は含まれません。
  • Lightning データサービス (LDS) ワイヤーアダプター: LDS アダプターは Lightning データサービスの動作を模倣します。データのプロパティに関する情報が含まれます。
  • Apex ワイヤーアダプター: Apex ワイヤーアダプターは Apex メソッドへのコールを模倣します。エラー状況が含まれます。

では、典型的な @wire デコレーターを見てみましょう。指定したインポート構文を使用して、ワイヤーアダプターをインポートします。プロパティや関数を @wire でデコレートして、ワイヤーアダプターを指定します。各ワイヤーアダプターがデータ型を定義します。

次のコードは、Account.Name 項目をインポートして、ワイヤーアダプターの設定オブジェクトで使用します。

import { LightningElement, api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import ACCOUNT_NAME_FIELD from '@salesforce/schema/Account.Name';
  
export default class Record extends LightningElement {
  @api recordId;
    
  @wire(getRecord, { recordId: '$recordId', fields: [ACCOUNT_NAME_FIELD] })
  wiredRecord;
}

詳しく見てみましょう。

  • 8 行目は @wire デコレーターを使用して、インポート済みの getRecord メソッドにアクセスし、$recordId リアクティブ変数を 1 つ目の引数として渡しています。2 つ目の引数は、3 行目のスキーマからインポートされた Account.Name への参照です。
  • 9 行目は、ワイヤーサービスからデータのストリームを受信する非公開のプロパティまたは関数です。プロパティの場合は、結果がそのプロパティの data プロパティまたは error プロパティに返されます。関数の場合は、結果が data プロパティと error プロパティが指定されているオブジェクトで返されます。

では、各種のアダプターを見ていきましょう。

汎用ワイヤーアダプターの使用

最初に、CurrentPageReference を設定した @wire サービスを使用します。

lightning-navigation サービスには、URL を生成したり、ページ参照に移動したりするためのワイヤーアダプターや関数が用意されています。ここでは CurrentPageReference を使用して Salesforce の現在のページへの参照を取得し、そのテストを作成します。

  1. Visual Studio Code で、lwc フォルダーを右クリックし、[SFDX: Create Lightning Web Component (SFDX: Lightning Web コンポーネントの作成)] を選択します。
  2. 新しいコンポーネントの名前に「wireCPR」と入力します。
  3. Enter キーを押します。
  4. Enter キーを押して、デフォルトの force-app/main/default/lwc を受け入れます。
  5. 新しい wireCPR/__tests__ フォルダーで、wireCPR.test.js ファイルを開きます。
  6. 新しいファイルを次のコードで上書きします。
    import { createElement } from 'lwc';
    import WireCPR from 'c/wireCPR';
    import { CurrentPageReference } from 'lightning/navigation';
      
    // Mock realistic data
    const mockCurrentPageReference = require('./data/CurrentPageReference.json');
      
    describe('c-wire-c-p-r', () => {
      afterEach(() => {
        while (document.body.firstChild) {
          document.body.removeChild(document.body.firstChild);
        }
      });
        
      it('renders the current page reference in <pre> tag', () => {
        const element = createElement('c-wire-c-p-r', {
          is: WireCPR
        });
        document.body.appendChild(element);
          
        // Select element for validation
        const preElement = element.shadowRoot.querySelector('pre');
        expect(preElement).not.toBeNull();
          
        // Emit data from @wire
        CurrentPageReference.emit(mockCurrentPageReference);
          
        return Promise.resolve().then(() => {
          expect(preElement.textContent).toBe(
            JSON.stringify(mockCurrentPageReference, null, 2)
          );
        });
      });
    });
  7. ファイルを保存して、テストを実行します。

詳しく見てみましょう。

  • 3 行目は、CurrentPageReference の新しいインポートです。
  • 6 行目は、PageReference というモックデータを記載したファイルを取得します。このファイルはまだ作成していないため、テストでエラーが発生する 1 つ目の理由になります。
    Test suite failed to run
      Cannot find module './data/CurrentPageReference.json' from 'wireCPR.test.js'
    後でこのエラーを修正します。
  • 26 行目で、emit() を使用してモックデータを入力します。
  • 28 行目以降は、モックデータが preElement に更新されることを想定する Promise です。

では、テストデータファイルを作成して、テストに合格するようコードを更新しましょう。まず、__tests__ ディレクトリにモックデータファイルを保存するディレクトリを作成します。

  1. __tests__ ディレクトリを右クリックし、[New Folder (新しいフォルダー)] を選択します。
  2. 新しいディレクトリの名前に「data」と入力します。
  3. Enter キーを押します。
  4. data ディレクトリを右クリックし、[New File (新しいファイル)] を選択します。
  5. 「CurrentPageReference.json」と入力します。
  6. Enter キーを押します。
  7. 新しいファイルに次の json コードブロックを入力します。
    {
      "type": "standard__navItemPage",
      "attributes": {
        "apiName": "Wire"
      },
      "state": {}
    }
  8. ファイルを保存して、テストを実行します。
  9. テストで次のエラーメッセージを取得します。
    expect(received).not.toBeNull()
      Received: null
    できました! テストに失敗することで、コードを順に確認しながら問題をいち早く特定して処理を促進できます。

次は、HTML コードと JavaScript コードを追加します。

  1. wireCPR.html を開きます。
  2. 次のコードを template タグの内側に追加します。
      <lightning-card title="Wire CurrentPageReference" icon-name="custom:custom67">
        <pre>{currentPageRef}</pre>
      </lightning-card>
  3. ファイルを保存します。
  4. wireCPR.js を開いて、コードを次のコードに置換します。
    import { LightningElement, wire } from 'lwc';
    import { CurrentPageReference } from 'lightning/navigation';
      
    export default class WireCPR extends LightningElement {
      @wire(CurrentPageReference) pageRef;
        
      get currentPageRef() {
        return this.pageRef ? JSON.stringify(this.pageRef, null, 2) : '';
      }
    }
  5. ファイルを保存して、テストを実行します。
  6. このテストに合格します。

何が行われたのかを確認しましょう。@wire アダプターが使用されている場合、サービスから返された情報が検索されます。そのデータのモックを作成し、実際にサービスをコールしてデータを取得する代わりにモックを使用する必要があります。モックがあれば、現時点で存在する項目のみがテストされ、範囲外の事項はテストされません。また、テストが迅速に実行されるようになります。

Lightning データサービスワイヤーアダプターの使用

次に、Lightning データサービス (LDS) に @wire を使用します。LDS を使用すると、カスタムオブジェクトや標準オブジェクトにすばやくアクセスできます。コンポーネントは、LDS を使用して Salesforce からデータを取得し、表示します。LDS アダプターを使用してデータが期待どおりに表示されることを検証するテストを作成します。

  1. Visual Studio Code で新しい Lightning Web コンポーネントを作成します。
  2. 「wireLDS」という名前を設定します。
  3. wireLDS.test.js テストファイルのコードを次のコードで上書きします。
    import { createElement } from 'lwc';
    import WireLDS from 'c/wireLDS';
    import { getRecord } from 'lightning/uiRecordApi';
      
    // Mock realistic data
    const mockGetRecord = require('./data/getRecord.json');
      
    describe('c-wire-l-d-s', () => {
      afterEach(() => {
        while (document.body.firstChild) {
          document.body.removeChild(document.body.firstChild);
        }
      });
        
      describe('getRecord @wire data', () => {
        it('renders contact details', () => {
          const element = createElement('c-wire-l-d-s', {
            is: WireLDS
          });
          document.body.appendChild(element);
            
          // Emit data from @wire
          getRecord.emit(mockGetRecord);
            
          return Promise.resolve().then(() => {
            // Select elements for validation
            const nameElement = element.shadowRoot.querySelector('p.accountName');
            expect(nameElement.textContent).toBe(
              'Account Name: ' + mockGetRecord.fields.Name.value
            );
              
            const industryElement = element.shadowRoot.querySelector('p.accountIndustry');
            expect(industryElement.textContent).toBe(
              'Industry: ' + mockGetRecord.fields.Industry.value
            );
              
            const phoneElement = element.shadowRoot.querySelector('p.accountPhone');
            expect(phoneElement.textContent).toBe(
              'Phone: ' + mockGetRecord.fields.Phone.value
            );
              
            const ownerElement = element.shadowRoot.querySelector('p.accountOwner');
            expect(ownerElement.textContent).toBe(
              'Owner: ' + mockGetRecord.fields.Owner.displayValue
            );
          });
        });
      });
    });
  4. ファイルを保存して、テストを実行します。
  5. モックデータがないため、テストに失敗します (この後でモックデータを作成します)。

その前に、何が行われたのかをテストコードで確認しましょう。

  • 3 行目は、getRecord の新しいインポートです。getRecord は LDS API から取得されます。
  • 6 行目で再び data ディレクトリの getRecord.json ファイルからデータをモックしています。
  • 23 行目で、mockGetRecord を引数として設定した getRecord で emit メソッドを使用します。
  • 25 行目以降は Promise 戻り値で、ここではさまざまな要素がモックデータを使用して更新されていることを確認します。

次に、テストに合格するためにモックデータファイルと残りのファイルを作成します。ファイルを作成するたびにテストを実行し、合格するまでテストエラーの解消状況を確認します。

  1. __tests__ ディレクトリに data ディレクトリを作成します。
  2. getRecord.json という名前のテストデータファイルを作成します。
  3. 次のコードを追加します。
    {
      "apiName" : "Account",
      "childRelationships" : { },
      "eTag" : "35f2effe0a85913b45011ae4e7dae39f",
      "fields" : {
        "Industry" : {
          "displayValue" : "Banking",
          "value" : "Banking"
        },
        "Name" : {
          "displayValue" : null,
          "value" : "Company ABC"
        },
        "Owner" : {
          "displayValue" : "Test User",
          "value" : {
            "apiName" : "User",
            "childRelationships" : { },
            "eTag" : "f1a72efecde2ece9844980f21b4a0c25",
            "fields" : {
              "Id" : {
                "displayValue" : null,
                "value" : "005o0000000KEEUAA4"
              },
              "Name" : {
                "displayValue" : null,
                "value" : "Test User"
              }
            },
            "id" : "005o0000000KEEUAA4",
            "lastModifiedById" : "005o0000000KEEUAA4",
            "lastModifiedDate" : "2019-08-22T23:45:53.000Z",
            "recordTypeInfo" : null,
            "systemModstamp" : "2019-08-23T06:00:11.000Z"
          }
        },
        "OwnerId" : {
          "displayValue" : null,
          "value" : "005o0000000KEEUAA4"
        },
        "Phone" : {
          "displayValue" : null,
          "value" : "867-5309"
        }
      },
      "id" : "0011J00001A3VFoQAN",
      "lastModifiedById" : "005o0000000KEEUAA4",
      "lastModifiedDate" : "2020-02-28T05:46:17.000Z",
      "recordTypeInfo" : null,
      "systemModstamp" : "2020-02-28T05:46:17.000Z"
    }
  4. ファイルを保存して、テストを実行します。
  5. テストに失敗します。
  6. wireLDS.html を開き、テンプレートタグの内側に次のコードを入力します。
      <lightning-card title="Wire Lightning Data Service" icon-name="custom:custom108">
        <template if:true={account.data}>
          <p class="accountName">Account Name: {name}</p>
          <p class="accountIndustry">Industry: {industry}</p>
          <p class="accountPhone">Phone: {phone}</p>
          <p class="accountOwner">Owner: {owner}</p>
        </template>
        <template if:true={account.error}>
          <p>No account found.</p>
        </template>
      </lightning-card>
  7. ファイルを保存して、テストを実行します。
  8. 今回もテストに失敗しますが、合格まであと一息です。データを取得する JavaScript コントローラーを追加するだけです。
  9. wireLDS.js を開き、そのすべてのコードを次のコードで上書きします。
    import { LightningElement, api, wire } from 'lwc';
    import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
    import NAME_FIELD from '@salesforce/schema/Account.Name';
    import OWNER_NAME_FIELD from '@salesforce/schema/Account.Owner.Name';
    import PHONE_FIELD from '@salesforce/schema/Account.Phone';
    import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
      
    export default class WireLDS extends LightningElement {
      @api recordId;
        
      @wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD, INDUSTRY_FIELD], optionalFields: [PHONE_FIELD, OWNER_NAME_FIELD] })
      account;
        
      get name() {
        return getFieldValue(this.account.data, NAME_FIELD);
      }
        
      get phone() {
        return getFieldValue(this.account.data, PHONE_FIELD);
      }
        
      get industry(){
        return getFieldValue(this.account.data, INDUSTRY_FIELD);
      }
        
      get owner() {
        return getFieldValue(this.account.data, OWNER_NAME_FIELD);
      }
    }
  10. ファイルを保存して、テストを実行します。
  11. このテストに合格します。
メモ

メモ

Lightning Web コンポーネントは、ユーザーインターフェース API でサポートされているすべてのカスタムオブジェクトと標準オブジェクトから Salesforce データやメタデータにアクセスします。外部オブジェクトはサポートされていません。

ユーザーインターフェース API にアクセスする REST クライアントを使用してデータのスナップショットを取得するという方法で、テストデータを取得します。この方法のほうが JSON を手動で記述するより正確です。以下は、上記のデータを取得する REST コールの一例です (自身のアカウント ID が必要です)。

/services/data/v47.0/ui-api/records/0011J00001A3VFo?fields=Account.Name,Account.Industry&optionalFields=Account.Phone,Account.Owner.Name

ところで、データの取得中にエラーが発生した場合はどうなるのでしょうか? この点もテストできます。では、wireLDS.test.js テストファイルに新しい describe ブロックを追加しましょう。

  1. 'getRecord @wire data’ describe ブロックの直後に次のコードを追加して、'c-wire-l-d-s’ describe ブロック内に配置されるようにします。テストを明確にするために、describe ブロックをネストすることもできます。
      describe('getRecord @wire error', () => {
        it('shows error message', () => {
          const element = createElement('c-wire-l-d-s', {
            is: WireLDS
          });
          document.body.appendChild(element);
            
          // Emit error from @wire
          getRecord.error();
            
          return Promise.resolve().then(() => {
            const errorElement = element.shadowRoot.querySelector('p');
            expect(errorElement).not.toBeNull();
            expect(errorElement.textContent).toBe('No account found.');
          });
        });
      });
  2. ファイルを保存して、テストを実行します。
  3. getRecordAdaptererror() メソッドを使用しているため、テストに合格します。この結果、モックデータにエラーが生じるため、account.error が true になります。

Apex ワイヤーアダプターの使用

Apex の説明に進み、@wire を使用してテストするにはどうすればよいかを確認します。

LWC がインポートする Apex クラスは外部接続と見なされるため、モックする必要があります。つまり、Apex クラスを作成しなくてもテストできるということです。ここですべきことは、Apex コールからの予想される応答をモックするだけです。この場合、期待されるのは Apex クラスから返される取引先が表示されることです。取引先が返された場合にはその取引先が表示され、返されない場合はメッセージが表示されることを期待するテストを作成します。

では、Apex コールを使用する LWC を作成しましょう。

  1. Visual Studio Code で新しい Lightning Web コンポーネントを作成します。
  2. 「wireApex」という名前を設定します。
  3. wireApex.test.js テストファイルのコードを次のコードで上書きします。
    import { createElement } from 'lwc';
    import WireApex from 'c/wireApex';
    import getAccountList from '@salesforce/apex/AccountController.getAccountList';
      
    // Realistic data with a list of contacts
    const mockGetAccountList = require('./data/getAccountList.json');
      
    // An empty list of records to verify the component does something reasonable
    // when there is no data to display
    const mockGetAccountListNoRecords = require('./data/getAccountListNoRecords.json');
      
    // Mock getAccountList Apex wire adapter
    jest.mock(
      '@salesforce/apex/AccountController.getAccountList',
      () => {
        const {
          createApexTestWireAdapter
        } = require('@salesforce/sfdx-lwc-jest');
        return {
          default: createApexTestWireAdapter(jest.fn())
        };
      },
      { virtual: true }
    );
      
    describe('c-wire-apex', () => {
      afterEach(() => {
        while (document.body.firstChild) {
          document.body.removeChild(document.body.firstChild);
        }
        // Prevent data saved on mocks from leaking between tests
        jest.clearAllMocks();
      });
        
      describe('getAccountList @wire data', () => {
        it('renders six records', () => {
          const element = createElement('c-wire-apex', {
            is: WireApex
          });
          document.body.appendChild(element);
            
          // Emit data from @wire
          getAccountList.emit(mockGetAccountList);
            
          return Promise.resolve().then(() => {
            // Select elements for validation
            const accountElements = element.shadowRoot.querySelectorAll('p');
            expect(accountElements.length).toBe(mockGetAccountList.length);
            expect(accountElements[0].textContent).toBe(mockGetAccountList[0].Name);
          });
        });
          
        it('renders no items when no records are returned', () => {
          const element = createElement('c-wire-apex', {
            is: WireApex
          });
          document.body.appendChild(element);
            
          // Emit data from @wire
          getAccountList.emit(mockGetAccountListNoRecords);
            
          return Promise.resolve().then(() => {
            // Select elements for validation
            const accountElements = element.shadowRoot.querySelectorAll('p');
            expect(accountElements.length).toBe(
              mockGetAccountListNoRecords.length
            );
          });
        });
      });
        
      describe('getAccountList @wire error', () => {
        it('shows error panel element', () => {
          const element = createElement('c-wire-apex', {
            is: WireApex
          });
          document.body.appendChild(element);
            
          // Emit error from @wire
          getAccountList.error();
            
          return Promise.resolve().then(() => {
            const errorElement = element.shadowRoot.querySelector('p');
            expect(errorElement).not.toBeNull();
            expect(errorElement.textContent).toBe('No accounts found.');
          });
        });
      });
    });
  4. ファイルを保存して、テストを実行します。
  5. モックデータが見つからないというエラーが表示されます。

このコードの大部分は以前と同じです。このクリーンアップコードには、テストの前後にモックをリセットする jest.clearAllMocks() という新しい項目があります。2 種類のテストに 2 つのモックファイルがあるため、この項目が必要になります。1 つ目のテストは、6 つの取引先を提示する Apex コールを探します。2 つ目のテストは、取引先が見つからない場合にどうなるかをアサートします。最後のテストは、Apex でエラーが発生した場合にどうなるかをアサートします。

では、モックデータファイルと残りのコードを追加しましょう。

  1. __tests__ ディレクトリに data ディレクトリを作成します。
  2. 新しい data ディレクトリに getAccountList.jsongetAccountListNoRecords.json という名前の 2 つのファイルを作成します。
  3. getAccountList.json に次のコードを入力します。
    [
      {
        "Id": "001o0000005w4fT",
        "Name": "Edge Communications"
      },
      {
        "Id": "001o0000005w4fa",
        "Name": "United Oil & Gas Corporation"
      },
      {
        "Id": "001o0000005w4fY",
        "Name": "Express Logistics and Transport"
      },
      {
        "Id": "001o0000005w4fV",
        "Name": "Pyramid Construction Inc."
      },
      {
        "Id": "001o0000005w4fX",
        "Name": "Grand Hotels & Resorts Ltd"
      },
      {
        "Id": "001o000000k2NMs",
        "Name": "ABC Genius Tech Consulting"
      }
    ]
  4. getAccountListNoRecords.json ファイルに次の空白の JSON オブジェクトが挿入されます。
    []
  5. 次に、wireApex.htmltemplate タグの内側に次のコードを入力します。
      <lightning-card title="Wire Apex" icon-name="custom:custom107">
        <template if:true={accounts}>
          <template for:each={accounts} for:item="account">
            <p key={account.Id}>{account.Name}</p>
          </template>
        </template>
        <template if:true={error}>
          <p>No accounts found.</p>
        </template>
      </lightning-card>
  6. 最後に wireApex.js のコードを次のコードに置換します。
    import { LightningElement, wire } from 'lwc';
    import getAccountList from '@salesforce/apex/AccountController.getAccountList';
      
    export default class WireApex extends LightningElement {
      accounts;
      error;
        
      @wire(getAccountList)
      wiredAccounts({ error, data }) {
        if(data) {
          this.accounts = data;
          this.error = undefined;
        } else if(error) {
          this.error = error;
          this.accounts = undefined;
        }
      }
    }
    ここでは、AccountController Apex クラスから getAccountList メソッドを取得しているに過ぎません。LWC でこのメソッドを機能させるためには、@AuraEnabled(cacheable=true) アノテーションを付加する必要があります。@wire がこのアノテーションを使用して、返された error または data を関数に入力します。
  7. すべてのファイルを保存して、テストを実行します。
  8. このテストに合格します。

次の単元では、他のコンポーネントのモックに挑戦し、Jest を使用して Lightning Web コンポーネントをテストするあらゆる方法を習得します。

リソース

無料で学習を続けましょう!
続けるにはアカウントにサインアップしてください。
サインアップすると次のような機能が利用できるようになります。
  • 各自のキャリア目標に合わせてパーソナライズされたおすすめが表示される
  • ハンズオン Challenge やテストでスキルを練習できる
  • 進捗状況を追跡して上司と共有できる
  • メンターやキャリアチャンスと繋がることができる