LWC测试-模拟其他组件

学习目标

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

  • 描述sfdx-lwc-jest软件包提供的存根。
  • 了解如何覆盖Jest配置。
  • 在开发环境之外测试组件。
  • 为外部组件创建存根。

模拟基础组件

sfdx-lwc-jest软件包使我们可以在Lightning Web Components上运行Jest。它设置了所有必要的Jest配置,以运行测试,而无需进行任何其他更改。我们已经在先前的模块中广泛使用了它。该软件包包括Lightning名称空间中所有组件的一组存根(模拟正在测试的模块所依赖的组件的程序)。这包括所有闪电基础组件。存根安装在src/lightning-stubs目录中的node-modules/@salesforce/sfdx-lwc-jest 目录中。

test-lwc项目中的lightning-stubs目录。

有时您需要覆盖提供的默认存根。这是通过覆盖Jest配置,然后创建自定义存根来完成的。让我们通过闪电按钮来完成此步骤。

首先在Visual Studio Code中为自定义存根设置目录。

  1. 右键单击force-app目录,然后选择“ 新建文件夹”
  2. 输入test新目录的名称。
  3. 右键单击新test目录,然后选择“ 新建文件夹”
  4. 输入jest-mocks新目录的名称。
  5. 右键单击新jest-mocks目录,然后选择“ 新建文件夹”
  6. 输入lightning新目录的名称。

这是Lightning Base Component存根的根。

接下来,通过更新在jest.config.jsSalesforce DX项目的根目录下命名的文件并从sfdx-lwc-jest导入默认配置来覆盖Jest配置。

  1. 打开jest.config.js文件。
  2. 在“在此处添加任何自定义配置”注释后,在新文件中输入以下代码块: moduleNameMapper: { '^lightning/button$': '<rootDir>/force-app/test/jest-mocks/lightning/button' }复制注意moduleNameMapper。这是在告诉Jest在哪里找到存根lightning-button。第一个破折号转换为正斜杠,其余的组件名称则从烤肉串到驼峰大小写。正斜杠的原因是因为模块解析器将第一个破折号之前的所有内容都视为名称空间。在这里,<rootDir>映射到Salesforce DX工作区的根。
  3. 保存文件。

现在,将button存根添加到lightning我们刚刚创建的目录中。

  1. 右键单击lightning在最后一步中创建的目录,然后选择New File
  2. 输入button.html新文件的名称。
  3. 在新文件中输入以下代码块:<template></template>复制
  4. 保存文件。
  5. 右键单击lightning目录,然后选择“ 新建文件”
  6. 输入button.js新文件的名称。
  7. 在新文件中输入以下代码块:import { LightningElement, api } from 'lwc'; export default class Button extends LightningElement { @api disabled; @api iconName; @api iconPosition; @api label; @api name; @api type; @api value; @api variant; }复制
  8. 保存文件。

这两个文件是lightning-stubs文件夹中的lightning-button文件的副本。如果需要,它们允许覆盖用于Jest测试的基本闪电按钮。

现在,我们可以覆盖sfdx-lwc-jest提供的默认存根。来自其他名称空间或托管包的组件又如何呢?我们可以通过一些调整来处理这些情况。接下来让我们深入探讨。

模拟其他组件

让我们从模拟具有不同名称空间的组件开始。为此,您设置了一个Jest测试失败的Lightning Web组件,然后我们将模拟出一个修复程序。

  1. 在Visual Studio Code中创建一个新的Lightning Web组件。
  2. 将名称设置为otherComponents
  3. 现在以另一种方式来创建Jest测试。在终端中运行以下CLI命令:sfdx force:lightning:lwc:test:create -f force-app/main/default/lwc/otherComponents/otherComponents.js复制

此命令自动创建__tests__文件夹和otherComponents.test.js测试文件。请注意,导入已经在那里,并且初始描述块已经在此处,并且初始测试失败。 

运行该测试失败,因为我们期望1为2。让我们对其进行修复,以便我们可以看到其他组件未存根时如何出错。

  1. 打开测试文件otherComponents.test.js并更新测试以期望1为1:expect(1).toBe(1);复制
  2. 保存文件并运行测试。
  3. 测试通过。

现在,我们向LWC添加具有不同名称空间的组件。

  1. 打开otherComponents.html文件并在模板标签之间添加以下代码:<thunder-hammer-button onclick={makeNoise}></thunder-hammer-button>复制
  2. 保存文件并运行测试。
  3. 测试由于新原因而失败:Test suite failed to run Cannot find module 'thunder/hammerButton' from 'otherComponents.html'复制

由于该<thunder-hammer-button>组件来自另一个名称空间,而不是位于本地lwc目录中,因此您需要创建一个存根并更新Jest配置,以将这些组件的名称映射到模拟文件。

首先,您需要添加一个雷鸣目录来表示名称空间。然后添加文件以将其存根。

  1. 右键单击jest-mocks目录中的force-app/test/目录,然后选择新建文件夹
  2. 输入thunder新目录的名称。
  3. 右键单击thunder目录,然后选择“ 新建文件”
  4. 输入hammerButton.html新文件的名称。
  5. 在新文件中输入以下代码块:<template></template>复制
  6. 保存文件。
  7. 右键单击新thunder目录,然后选择“ 新建文件”
  8. 输入hammerButton.js新文件的名称。
  9. 在新文件中输入以下代码块:import { LightningElement, api } from 'lwc'; export default class HammerButton extends LightningElement { @api label; // any other implementation you may want to expose here }复制
  10. 保存文件并运行测试。
  11. 测试失败。我们需要更新Jest配置文件。
  12. 打开jest.config.js并添加以下moduleNameMapper: {行:'^thunder/hammerButton$': '<rootDir>/force-app/test/jest-mocks/thunder/hammerButton',复制
  13. 保存文件并运行测试。
  14. 太棒了!测试通过。

有时,您使用的LWC会导入另一个不在Salesforce DX项目的本地LWC目录中的LWC。因为它是托管软件包的一部分,或者没有与源文件一起下载。没有存根,它们将导致Jest失败。让我们测试一下。

  1. 打开otherComponents.html文件,并在第一个模板标记后添加以下代码:<c-display-panel errors={error} notes={messages}></c-display-panel>复制
  2. 保存文件并运行测试。
  3. 测试失败,因为找不到组件。

您可以使用存根修复它。首先创建存根,然后更新Jest配置。

  1. 右键单击jest-mocks目录中的force-app/test/目录,然后选择新建文件夹
  2. 输入c新目录的名称。
  3. 右键单击c目录,然后选择“ 新建文件”
  4. 输入displayPanel.html新文件的名称。
  5. 在新文件中输入以下代码块:<template></template>复制
  6. 保存文件。
  7. 右键单击新c目录,然后选择“ 新建文件”
  8. 输入displayPanel.js新文件的名称。
  9. 在新文件中输入以下代码块:import { LightningElement, api } from 'lwc'; export default class ErrorPanel extends LightningElement { @api errors; @api notes; // any other implementation you may want to expose here }复制请注意,组件调用中传递的每个参数都有一个api装饰器。
  10. 保存文件。
  11. 打开jest.config.js并添加以下moduleNameMapper: {行:'^c/displayPanel$': '<rootDir>/force-app/test/jest-mocks/c/displayPanel',复制
  12. 保存文件并运行测试。
  13. 测试通过。

做得好!现在,您可以为所有Lightning Web组件编写测试。您还可以根据需要使这些存根实现变得复杂或简单。

LWC测试-编写一个针对wire的Jest测试

学习目标

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

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

测试@Wire服务

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

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

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

测试要求您完全控制测试消耗的输入。不允许任何外部代码或数据依赖项。我们从中导入测试实用程序API sfdx-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使我们可以快速访问自定义和标准对象。

  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。
  • 第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调用的预期响应。 

让我们构建一个使用它的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. 您会因缺少的模拟数据文件而出错。

大多数代码是熟悉的。jest.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.jsimport { 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组件的方法。

LWC测试-写一个Jest测试

学习目标

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

  • 编写测试以验证您的设置。
  • 编写失败的测试,然后更改组件以使其通过。
  • 识别基本的Jest命令。
  • 解释生命周期挂钩。

从Lightning Web组件开始

要测试Lightning Web组件,我们首先必须有一个要测试的组件。

创建一个闪电Web组件

  1. 在Visual Studio Code中,通过按Ctrl + Shift + P (Windows)或Cmd + Shift + P (macOS)打开命令面板。
  2. 输入lightning web
  3. 选择SFDX:创建Lightning Web组件。不要使用SFDX:创建闪电组件。(这将创建一个Aura组件。)
  4. 输入unitTest新组件的名称。
  5. Enter键
  6. 再次按Enter接受默认值force-app/main/default/lwc

这将在lwc目录中创建带有初始基本文件的unitTest目录。

test-lwc项目中的unitTest目录。

编写基本测试

Jest测试的编写,保存和运行方式与为Aura Components的闪电测试服务编写的Jasmine或Mocha测试不同。笑话测试仅在本地进行,并且独立于Salesforce保存和运行。实际上,如果尝试将Jest测试部署到Salesforce组织中,则会出现错误。尽管针对Lightning Web组件的Jest测试未部署到您的Salesforce组织中,但请确保将它们与组件本身一起提交到版本控制。 

创建__tests__文件夹

测试文件需要与其他组件文件分开。在组件的bundle文件夹的顶层创建一个名为__tests__的文件夹。将此组件的所有测试保存在__tests__文件夹中。 

  1. 在Visual Studio Code中,右键单击unitTest目录,然后选择“ 新建文件夹”
  2. 输入__tests__
  3. Enter键

配置.forceignore

通过将__tests__文件夹提交到版本控制,与其他团队成员或系统共享测试。它们是您的项目和持续集成过程的重要组成部分。为了防止将它们部署到Salesforce,.forceignore文件为此输入了一个排除项。 

确保您的项目.forceignore文件包含以下排除项。如果没有,请添加它并保存文件。

# LWC configuration files
**/jsconfig.json
**/.eslintrc.json
# LWC Jest
**/__tests__/**

创建Jest测试文件

  1. 在Visual Studio Code中,右键单击__tests__目录,然后选择New File
  2. 输入sum.test.js
  3. Enter键
  4. 在新的测试文件中输入以下代码:

import { sum } from ‘../sum’;
describe(‘sum()’, () => {
it(‘should add 1 and 2 returning 3’, () => {
expect(sum(1, 2)).toBe(3);
});
});

保存文件

运行测试

  1. 在Visual Studio Code中,选择“ 查看” ,然后选择“ 终端”。这将在Visual Studio Code中打开一个终端。终端默认为当前项目的顶级目录。
  2. 在终端中,从上一个单元执行以下命令:npm run test:unit
  3. 由于缺少求和函数,测试失败。

让我们看看如何解决它。 

  1. 在Visual Studio Code中,右键单击unitTest目录,然后选择New File
  2. 输入sum.js
  3. Enter键
  4. 在新文件中输入以下代码块:export function sum(x, y) { return x + y; }复制
  5. 保存文件。
  6. 在终端中再次运行测试:npm run test:unit复制
  7. 测试通过。


恭喜你!您刚刚确认Jest已安装并正常工作。

让我们看一下测试代码,看看发生了什么。

import { sum } from '../sum';
  
describe('sum()', () => {
  it('should add 1 and 2 returning 3', () => {
    expect(sum(1, 2)).toBe(3);
  });
});
  • 第1行sum从sum JavaScript文件导入导出的函数。
  • 第3行是Jest测试套件的开始。该describe函数或块是一个测试套件,并接受两个参数。首先是对我们要测试的单位的描述,通常以名词的形式出现。第二个是保存一个或多个测试的回调函数。您还可以将describe测试套件相互嵌套以提高清晰度。
  • 第4行是测试(it是的别名test)。该it函数或块也接受两个参数。首先是对我们期望的另一种描述,通常以动词开头。然后是一个建立测试并保存测试的断言或期望的回调函数。
  • 第5行是expect声明成功条件的语句:该sum函数将添加两个参数1和2,并返回3。它toBe是许多 Jest匹配器之一。
    在第5行之后的以下行添加另一个断言: expect(sum(1, 2)).not.toBeGreaterThan(3);
  • 添加.not.toBeGreaterThan确保该数字不大于3。您可以使用添加另一个Expect语句.not.toBeLessThan(3)。 

现在进行Lightning Web组件测试。

对Lightning Web组件的Jest测试应隔离测试单个组件的行为,并且对外部组件或服务的依赖性最小。

再次执行该过程以创建unitTest测试文件:

  • 右键单击__tests__目录,然后选择“ 新建文件”
  • 输入unitTest.test.js
  • Enter键
  • 在新的测试文件中输入以下代码:

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行是新的。它createElementlwc框架导入方法。仅在测试中可用。
  • 第2行UnitTest从组件JavaScript控制器导入该类。
  • 第4行开始describe测试套件块。
  • 第5行是一个Jest清理方法。afterEach()是Jest的设置和清除方法之一。afterEach()在描述块中的每个测试之后运行。此afterEach()方法在测试结束时重置DOM。测试运行时,Jest没有运行浏览器。Jest使用jsdom提供了行为类似于浏览器的DOM或文档的环境。每个测试文件都有一个jsdom实例,并且不会在文件内部的测试之间重置更改。最好在两次测试之间进行清理,以使测试的输出不会影响任何其他测试。还有其他设置和清除方法。检查资源。
  • 第12行启动it测试块。
  • 第13行是您使用导入createElement方法的位置。它创建组件的实例并将其分配给常量element
  • 第16 expect行将断言unitNumber变量设置为5。这是我们要测试的第一个要求,首先unitNumber设置为5。
  • 第18行实际上使用将该方法添加element到jsdom的版本中。该调用将Lightning Web组件附加到DOM并进行渲染,这也意味着将调用生命周期挂钩connectedCallback()和renderedCallback()(稍后会对此进行更多介绍)。document.bodyappendChild
  • 第20行使用querySelector(标准DOM查询方法)在DOM中搜索div标签。使用element.shadowRoot作为父的查询。这是一个仅测试的API,可让您窥视阴影边界以检查组件的阴影树。
  • 最后,第21行有expecttextContent的的div标签来断言“单元5还活着!” 在那儿。这是最终要求。声明文本正确。

要使测试通过,您需要将代码添加到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。Jest在结束测试之前等待Promise链完成。如果Promise以拒绝状态结束,则Jest无法通过测试。

  1. 打开unitTest.test.js
  2. 在最后一个测试之后添加第二个测试。 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!'); });复制
  3. 保存文件并运行测试。npm run test:unit复制
  4. 您收到以下失败消息:Expected: "Unit 6 alive!" Received: "Unit 5 alive!"复制

这是怎么回事?该expect声明断言的div.textContext应该是“ Unit 6 live”,但仍然是“ Unit 5 live!”。为了查看更改,我们需要通过返回resolve来等待它Promise。 

  1. expect在注释之后,将失败的语句替换为以下内容// Verify display unit statusexpect(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!'); });
  2. 使用与上次相同的命令来运行测试,或者使用上一单元的“运行Jest测试”部分中的其他选项之一。
  3. 测试通过。

到目前为止,一切都很好。您在两个测试套件中有三个成功的测试。接下来,添加第四项测试,以便在输入字段更新时可以验证单元状态是否已更新。为此,请在输入字段中使用change事件。

  1. 打开unitTest.test.js,如果它不是已经打开。
  2. 在您添加的最后一个测试之后添加一行,并将第三个测试添加到套件中: 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!'); }); });
  3. 保存文件并运行测试以查看失败的消息。
    您可以看到只运行了一个测试,而其他两个则被跳过。

让我们看一下正在测试的内容:

  • 前几行应该很熟悉。您正在将UnitTest添加到中document.body,然后创建对的引用div
  • 该常数inputElement是参考雷电输入字段设置的。
  • 接下来,将该输入字段的值设置为7。
  • 然后,我们使用事件类型为“ change”的事件dispatchEvent来触发事件CustomEvent
  • Promise是熟悉的,只有改变改变的输入字段的值。

让我们更新代码以使其通过。为此,将添加lightning-input到HTML文件,并将handleChange方法添加到JavaScript控制器。

  1. 打开unitTest.html
  2. 将以下代码添加到中的lightning-card和之前div <lightning-input label="Unit Number" value={unitNumber} onchange={handleChange} > </lightning-input>复制
  3. 保存文件。
  4. 打开unitTest.js
  5. @api unitNumber语句之后添加以下代码: handleChange(event) { this.unitNumber = event.target.value; }复制
  6. 保存文件并运行测试。
  7. 由于添加了input元素和JavaScript处理程序,因此测试通过。

测试生命周期挂钩

闪电Web组件具有由框架管理的生命周期。该框架创建组件,从DOM中添加和删除它们,并在组件状态改变时呈现DOM更新。有几种与生命周期交互的方法。 

connectedCallback()组件插入DOM时会触发生命周期挂钩。在disconnectedCallback()当组件从DOM除去生命周期钩闪光。这些挂钩的一种用途是注册和注销事件监听器。 

看一下 lwc-recipes示例存储库中pubsubContactList中的代码,其中 有一些很好的示例。

接下来,我们看一下为有线服务编写Jest测试。