闪电Web组件测试 – 编写一个针对wire的Jest测试

学习目标

完成本单元后,您将能够:

  • 列出用于有线服务的三个主要适配器。
  • 解释有线服务的模拟数据。
  • 了解反应变量及其影响。

测试@Wire服务

Lightning Web组件使用基于Lightning Data Service构建的电抗线服务来读取Salesforce数据。组件@wire在其JavaScript类中使用以从lightning/ui*Api模块中的一个有线适配器读取数据。

有线服务之所以具有响应性,部分原因是它支持响应变量。反应变量以开头$。当电抗变量发生变化时,有线服务会提供新数据。如果数据存在于客户端缓存中,则可能不涉及网络请求。

我们使用@salesforce/sfdx-lwc-jest测试实用程序来测试这些组件如何处理有线服务中的数据和错误。

测试要求您完全控制测试消耗的输入。不允许任何外部代码或数据依赖项。我们从中导入测试实用程序APIsfdx-lwc-jest来模拟数据,因此我们的测试不依赖于不可预测的因素,例如远程调用或服务器延迟。

有三个用于模拟有线服务数据的适配器。

  • 通用电线适配器:调用emit()API时,通用适配器会按需发出数据。它不包含有关数据本身的任何其他信息。
  • 闪电数据服务(LDS)有线适配器:LDS适配器模仿闪电数据服务行为,并包含有关数据属性的信息。
  • 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变量作为其第一个参数传递。第二个参数是Account.Name 对第3行中从架构导入的引用。
  • 第9行可以是私有属性,也可以是从有线服务接收数据流的函数。如果是属性,则结果将返回到该属性的data属性或error属性。如果是函数,则结果将在具有data属性和error属性的对象中返回。

现在让我们看一下不同的适配器。

使用通用电线适配器

首先,我们将@wire服务与CurrentPageReference一起使用。

闪电导航服务提供有线适配器和功能,以生成URL或导航到页面引用。我们将用于CurrentPageReference获取对Salesforce中当前页面的引用并为其创建测试。

  1. 在Visual Studio Code中,右键单击该lwc文件夹,然后选择SFDX:Create Lightning Web Component
  2. 输入wireCPR新组件的名称。
  3. Enter键
  4. Enter 接受默认值force-app/main/default/lwc
  5. 右键单击刚创建的wireCPR目录,然后选择“新建文件夹”
  6. 输入__tests__
  7. Enter键
  8. 右键单击__tests__目录,然后选择“新建文件”
  9. 输入wireCPR.test.js
  10. Enter键
  11. 在新文件中输入以下代码块:
    import { createElement } from 'lwc';
    import WireCPR from 'c/wireCPR';
    import { CurrentPageReference } from 'lightning/navigation';
    import { registerTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
      
    // Mock realistic data
    const mockCurrentPageReference = require('./data/CurrentPageReference.json');
      
    // Register a standard test wire adapter.
    const currentPageReferenceAdapter = registerTestWireAdapter(
      CurrentPageReference
    );
      
    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
        currentPageReferenceAdapter.emit(mockCurrentPageReference);
      
        return Promise.resolve().then(() => {
          expect(preElement.textContent).toBe(
            JSON.stringify(mockCurrentPageReference, null, 2)
          );
        });
      });
    });
  12. 保存文件并运行测试。

让我们仔细看看。

  • 第3行和第4行有两个新导入:CurrentPageReferenceregisterTestWireAdapter
  • 第7行抓取具有模拟PageReference数据的文件。我们尚未创建此文件,因此这是测试出错的第一个原因。
    Test suite failed to run
      Cannot find module './data/CurrentPageReference.json' from 'wireCPR.test.js'
  • 接下来,我们将解决此问题。
  • 第10行,我们正在使用的registerTestWireAdapterCurrentPageReference进口的我们。这使我们可以在测试的稍后阶段对其进行模拟。
  • 第32行是我们使用填充模拟数据的地方emit()
  • 第34行启动Promise,它希望将模拟数据更新到中preElement

让我们创建测试数据文件并更新代码以使测试通过。首先,在目录下创建一个新目录__tests__来存储模拟数据文件。

  1. 右键单击__tests__目录,然后选择“新建文件夹”
  2. 输入data新目录的名称。
  3. Enter键
  4. 右键单击data目录,然后选择“新建文件”
  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适配器时,它将查找从服务返回的信息。我们需要创建该数据的模拟,以代替实际对服务的调用以获取数据。这样可以使我们仅测试当前拥有的项目,而不测试我们范围以外的项目。这也有助于保持测试速度。

使用闪电数据服务线适配器

接下来,我们使用@wire闪电数据服务(LDS)。LDS使我们可以快速访问自定义和标准对象。我们的组件使用LDS从Salesforce获取数据并显示它。我们将创建测试以验证使用LDS适配器可以按预期显示数据。

  1. 在Visual Studio Code中创建一个新的Lightning Web组件。
  2. 将名称设置为wireLDS
  3. 创建__tests__目录。
  4. 添加一个名为的测试文件wireLDS.test.js
  5. 将代码添加到测试文件:
    import { createElement } from 'lwc';
    import WireLDS from 'c/wireLDS';
    import { getRecord } from 'lightning/uiRecordApi';
    import { registerLdsTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
      
    // Mock realistic data
    const mockGetRecord = require('./data/getRecord.json');
      
    // Register as an LDS wire adapter
    const getRecordAdapter = registerLdsTestWireAdapter(getRecord);
      
    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
          getRecordAdapter.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
            );
          });
        });
      });
    });
  6. 保存文件并运行测试。
  7. 由于我们接下来创建的缺少的模拟数据文件,测试失败。

在此之前,让我们看一下测试代码以了解发生了什么。

  • 第3行和第4行有新的导入:getRecordregisterLdsTestWireAdapter
    getRecord来自LDS API。registerLdsTestWireAdapter是用于LDS的特定Jest适配器。
  • 第7行再次从目录中的getRecord.json文件中模拟数据data
  • 第10行向LDS电线适配器注册,getRecord因此我们准备使用模拟数据。我们在第27行执行的操作。
  • 行27个使用EMIT方法getRecordAdaptermockGetRecord作为参数。
  • 第29行开始Promise返回,我们检查是否已使用模拟数据更新了各种元素。

接下来,我们创建模拟数据文件和其余文件以通过测试。在创建每个文件之后,我们运行测试以查看测试错误的进展,直到它们通过为止。

  1. data目录中创建__tests__目录。
  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数据和元数据。不支持外部对象。

通过使用REST客户端访问用户界面API捕获数据快照来获取测试数据 。这种方法比手工编写JSON更准确。这是REST调用的示例,以获取上述数据(您需要自己的帐户ID):

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

但是,如果获取数据时出现错误怎么办?您也可以对此进行测试。让我们在测试文件中添加一个新的describe块wireLDS.test.js

  1. 在describe’getRecord @wire data’块之后立即添加以下代码,使其位于describe’c-wire-lds’块内部。您可以嵌套描述块以帮助澄清测试。
      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
          getRecordAdapter.error();
      
          return Promise.resolve().then(() => {
            const errorElement = element.shadowRoot.querySelector('p');
            expect(errorElement).not.toBeNull();
            expect(errorElement.textContent).toBe('No account found.');
          });
        });
      });
  2. 保存文件并运行测试。
  3. 测试通过,因为您正在使用上的error()方法getRecordAdapter。这将导致模拟数据出错,因此account.error将为true。

使用Apex线适配器

接下来,让我们深入了解Apex,看看如何使用@wire它进行测试。

LWC正在导入的Apex类被认为是需要模拟的外部连接。这意味着我们无需创建Apex类即可进行测试。我们需要做的只是模拟来自Apex调用的预期响应。在这种情况下,我们希望显示从Apex类返回的帐户。我们将创建测试,这些测试期望在返回帐户时显示帐户,并在没有返回消息的情况下显示消息。

让我们构建使用它的LWC。

  1. 在Visual Studio Code中创建一个新的Lightning Web组件。
  2. 将名称设置为wireApex
  3. 创建__tests__目录。
  4. 添加一个名为的测试文件wireApex.test.js
  5. 将以下代码添加到测试文件:
    import { createElement } from 'lwc';
    import WireApex from 'c/wireApex';
    import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
    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');
      
    // Register as Apex wire adapter. Some tests verify that provisioned values trigger desired behavior.
    const getAccountListAdapter = registerApexTestWireAdapter(getAccountList);
      
    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
          getAccountListAdapter.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
          getAccountListAdapter.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
          getAccountListAdapter.error();
      
          return Promise.resolve().then(() => {
            const errorElement = element.shadowRoot.querySelector('p');
            expect(errorElement).not.toBeNull();
            expect(errorElement.textContent).toBe('No accounts found.');
          });
        });
      });
    });
  6. 保存文件并运行测试。
  7. 您会因缺少的模拟数据文件而出错。

大多数代码是熟悉的。请注意,我们使用registerApexTestWireAdapter代替Apex与Apex一起使用registerLdsTestWireAdapterjest.clearAllMocks()清理代码中有一个新项目,用于重置测试之间的模拟。这是必需的,因为我们有两个模拟文件用于两个不同的测试。第一个测试正在寻找Apex调用以提供六个帐户。第二项测试是断言如果找不到帐户,将会发生什么情况。最后是测试,以断言如果Apex出错会发生什么情况。

让我们添加模拟数据文件和其余代码。

  1. data目录中创建__tests__目录。
  2. data名为getAccountList.json和的新目录中创建两个文件getAccountListNoRecords.json
  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. 现在,在template标记之间输入此代码wireApex.html
      <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;
        }
      }
    }

    请注意,我们仅从 Apex类获取 getAccountList 方法 AccountController。请记住,该方法必须带有注释, @AuraEnabled(cacheable=true) 以使其与LWC一起使用。该@wire用它来用一个函数 error 或 data 返回。

  7. 保存所有文件并运行测试。
  8. 测试通过。

在下一个单元中,您将处理其他组件的模拟,并完成使用Jest测试Lightning Web组件的方法。