闪电Web组件测试 – 模拟其他组件

学习目标

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

  • 描述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在哪里找到Stub 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 DisplayPanel 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组件编写测试。您还可以根据需要使这些存根实现变得复杂或简单。

闪电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组件的方法。

闪电Web组件测试 – 写一个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目录。

编写基本测试

玩笑测试的编写,保存和运行方式与为Aura Components的闪电测试服务编写的茉莉或摩卡测试不同。Jest测试仅在本地进行,并且独立于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测试文件

我们的第一个测试很简单。我们有一个sum()函数,它希望将两个数字相加并作为参数传递给它。

  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);
      });
    });
     
  5. 保存文件。

运行测试

  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测试文件

此测试将验证是否设置了属性,并将其添加到DOM后将显示正确的文本。

  1. 右键单击__tests__目录,然后选择“新建文件”
  2. 输入unitTest.test.js
  3. Enter键
  4. 在新的测试文件中输入以下代码:
    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!');
      });
    });
     
  5. 保存文件。
  6. 在终端中再次运行测试:
    npm run test:unit
     
  7. 测试失败:
    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
  • 第16expect行将断言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文件中添加代码。我们将添加代码以满足要求。

  1. 单击该unitTest.html文件将其打开。
  2. 覆盖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>
     
  3. 保存文件。
  4. 单击该unitTest.js文件将其打开并用以下命令覆盖:
    import { LightningElement, api } from 'lwc';
    import { sum } from './sum';
      
    export default class UnitTest extends LightningElement {
      @api unitNumber = sum(2,3);
    }
     
  5. 保存文件并运行测试:
    npm run test:unit
     
  6. 所有测试通过。

测试异步DOM更新

当Lightning Web组件的状态更改时,DOM异步更新。为确保您的测试在评估结果之前等待更新完成,请返回已解决的Promise。为此,请将其余测试代码链接到已解决的Promise。Jest在结束测试之前等待Promise链完成。如果Promise以拒绝状态结束,则Jest无法通过测试。

  1. 打开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!');
      });
     
  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 status
        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!');
        });
     
  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示例存储库中lmsSubscriberWebComponent中的代码,这 是一个很好的示例。

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

闪电Web组件测试 – 写一个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目录。

编写基本测试

玩笑测试的编写,保存和运行方式与为Aura Components的闪电测试服务编写的茉莉或摩卡测试不同。Jest测试仅在本地进行,并且独立于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测试文件

我们的第一个测试很简单。我们有一个sum()函数,它希望将两个数字相加并作为参数传递给它。

  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);
      });
    });
     
  5. 保存文件。

运行测试

  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测试文件

此测试将验证是否设置了属性,并将其添加到DOM后将显示正确的文本。

  1. 右键单击__tests__目录,然后选择“新建文件”
  2. 输入unitTest.test.js
  3. Enter键
  4. 在新的测试文件中输入以下代码:
    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!');
      });
    });
     
  5. 保存文件。
  6. 在终端中再次运行测试:
    npm run test:unit
     
  7. 测试失败:
    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
  • 第16expect行将断言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文件中添加代码。我们将添加代码以满足要求。

  1. 单击该unitTest.html文件将其打开。
  2. 覆盖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>
     
  3. 保存文件。
  4. 单击该unitTest.js文件将其打开并用以下命令覆盖:
    import { LightningElement, api } from 'lwc';
    import { sum } from './sum';
      
    export default class UnitTest extends LightningElement {
      @api unitNumber = sum(2,3);
    }
     
  5. 保存文件并运行测试:
    npm run test:unit
     
  6. 所有测试通过。

测试异步DOM更新

当Lightning Web组件的状态更改时,DOM异步更新。为确保您的测试在评估结果之前等待更新完成,请返回已解决的Promise。为此,请将其余测试代码链接到已解决的Promise。Jest在结束测试之前等待Promise链完成。如果Promise以拒绝状态结束,则Jest无法通过测试。

  1. 打开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!');
      });
     
  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 status
        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!');
        });
     
  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示例存储库中lmsSubscriberWebComponent中的代码,这 是一个很好的示例。

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

闪电Web组件测试 – 设置Jest测试框架

学习目标

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

  • 描述Jest测试框架。
  • 描述Node.js和npm的角色。
  • 安装Node.js和npm。
  • 在Salesforce DX项目中安装@ salesforce / sfdx-lwc-jest JavaScript模块。

在你开始之前

要完成此模块,您需要安装和更新Salesforce CLI,Visual Studio Code和用于Visual Studio Code的Salesforce Extensions。为了满足这些先决条件,我们建议您在继续之前完成 快速入门:Salesforce DX, 快速入门:Visual Studio Code for Salesforce开发和 快速入门:Lightning Web Components 项目。

创建一个Salesforce DX项目

测试之前的第一步是创建一个Salesforce DX项目,以存储您的Lightning Web组件和Jest测试。

  1. 在Visual Studio Code中,通过按Ctrl + Shift + P (Windows)或Cmd + Shift + P (macOS)打开命令面板。
  2. 输入sfdx
  3. 选择SFDX:创建项目。如果看不到此选项,请在继续之前完成本模块第一个单元中的前提条件。
  4. 选择标准
  5. 输入test-lwc作为项目名称。
  6. Enter键
  7. 选择一个文件夹来存储项目。
  8. 单击创建项目, 然后等待新的Visual Studio Code窗口打开。
  9. 单击查看,然后选择终端。这将在Visual Studio Code中打开一个终端窗口。终端默认为项目的顶级目录。您稍后需要终端在该项目的工作目录中运行命令。

什么是Node.js和npm?

Node.js是一个基于Chrome的V8 JavaScript引擎的JavaScript运行时,而npm是一个用于分发可重用代码模块的程序包管理器。在Node.js和npm的世界中,这些可重用的代码模块称为Node模块。在Salesforce术语中,Node模块(可以轻松地分发到多个项目的可重用代码)类似于 解锁包。

Node.js和npm是现代JavaScript开发人员工具箱中流行的工具。在“ 学习使用JavaScript” 路径中了解有关现代JavaScript的更多信息 。

安装Node.js和npm

Jest是一个Node模块,因此要使用它,您需要安装Node和npm。现在开始吧。

  1. 从安装Node.js的 https://nodejs.org/en/download/。我们建议使用LTS(长期支持)版本。
  2. 确认已安装Node.js。在我们先前打开的Visual Studio Code终端中,输入以下命令。node --version复制
  3. 您应该会看到类似的输出v12.13.0或更高版本。
  4. 当您安装Node.js时,npm也会自动安装。
    在终端中,输入以下命令。npm --version复制您应该会看到类似的输出6.13.0或更高版本。注意您可能需要更新npm,因此请访问 npmjs文档 以获取有关针对不同操作系统升级npm的更多详细信息。

什么是Jest?

Jest是功能强大的工具,具有编写JavaScript测试的丰富功能。Jest可以收集代码覆盖率信息,并支持模拟以帮助将测试与复杂的依赖项隔离。玩笑测试不会在浏览器中运行或连接到组织,因此运行速度很快。使用Jest编写所有Lightning Web组件的单元测试。要对Lightning Web组件运行Jest测试,您需要在Salesforce DX项目中使用@ salesforce / sfdx-lwc-jest节点模块。

注意

注意

Jest测试仅适用于Salesforce DX项目中的Lightning Web组件,不适用于Aura组件。对于Aura组件,请参阅 使用Lightning Testing Service测试组件。

安装sfdx-lwc-jest节点模块

@salesforce/sfdx-lwc-jest节点模块,您可以编写,运行和调试玩笑试验闪电Web组件。通过Salesforce CLI,可以轻松地将Jest及其依赖项安装到项目中。

  1. 在Visual Studio Code终端中,在Salesforce DX项目的顶级目录中运行以下命令:sfdx force:lightning:lwc:test:setup复制这将安装npm并@salesforce/sfdx-lwc-jest进入项目。结果应如下所示:
Setup messages in terminal.

您可能会注意到该过程发现了一些漏洞。就我们的目的而言,这很好。这些不是您要查找的漏洞。

注意

注意

对于其他安装Jest Node模块的方式,Salesforce @salesforce/sfdx-lwc-jest在https://www.npmjs.com/package/@salesforce/sfdc-lwc-jest上的npm上以JavaScript软件包的形式提供Node模块, 并以开源项目的形式提供该模块 在GitHub上 https://github.com/salesforce/sfdx-lwc-jest上。

运行Jest测试

太好了,您设置了Salesforce DX项目以能够运行稍后在此模块中编写的Jest测试。现在已经完成了所有工作,有几种方法可以运行Jest测试。您可以直接调用脚本,使用npm命令,也可以使用Visual Studio Code中的单击。您可以在文件或项目中运行一个测试或所有测试。当测试涵盖的代码更改时,您甚至可以自动运行测试。

让我们看一下运行Jest测试的不同方法。

sfdx-lwc-jest节点命令

您可以使用以下Node命令直接从项目中的脚本安装位置运行脚本。

  1. 在Visual Studio Code终端中,在Salesforce DX项目的顶级目录中,输入以下命令。node node_modules/@salesforce/sfdx-lwc-jest/bin/sfdx-lwc-jest目前还没有Jest测试,因此您应该看到类似的输出No tests found, exiting with code 1

这是一个很好的开始,但是让我们看一下如何通过自动化使它变得更好。

使用Package.json和npm自动执行测试脚本

进行单元测试的目的是促进开发人员在其开发和持续集成过程中编写和运行它们,以便尽早发现并修复错误。一遍又一遍地记住和键入长命令对您的目标适得其反。自动化就在这里。

npm具有一些非常好的现成的脚本自动化灵活性。早期运行安装会scripts在项目根目录的package.json文件的属性中添加了一系列选项。

{
  "name": "test-lwc",
  ...  "scripts": {
    ...
    "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",
    ...
  },
  ...}

如果要为项目运行所有测试,请从项目的基本目录运行此npm命令。

npm run test:unit

如果要在特定目录中运行测试,则在特定目录中使用上面的命令将仅在该目录中运行测试。这使您可以隔离正在测试的内容。

在开发过程中连续运行测试

要在每次保存更改时对单个组件运行所有测试,请将目录更改为组件目录,然后运行下面的npm命令,该命令将sfdx-lwc-jest与该--watch参数一起使用。如上所述,您还可以从项目的基础上运行此代码,并针对每个更改运行项目中的所有测试。Git需要初始化--watch才能从命令行工作。

npm run test:unit:watch

初始化Git后,Jest现在会监视所有组件文件的更新,并在每次检测到更改时运行所有相关测试。

在Jest调试模式下运行测试

要以调试模式运行项目的Jest测试,请运行下面的npm命令,该命令将sfdx-lwc-jest与--debug参数一起使用。 

npm run test:unit:debug

有关对Jest问题进行故障排除的信息,请参阅《 Jest:故障排除》。

运行测试并显示代码覆盖率

要查看测试的代码覆盖率,请使用以下--coverage选项。 

npm run test:unit:coverage

在Visual Studio Code中通过单击运行测试

Salesforce Visual Studio Code扩展为运行Jest测试提供了很多控制和视觉反馈。它为您提供了运行单个,多个或所有测试的选项。--watch由于Git已预安装在Visual Studio Code中,因此它还使您能够在文件上使用该选项。 

测试烧杯按钮图标。
测试图标

单击测试烧杯图标 以打开“测试”侧栏。如果看不到该图标,则可能需要创建一个新的SFDX项目。在“测试”侧栏中,有一个LWC测试部分,显示项目中的所有Jest测试。这是项目中的测试侧边栏的外观。

在test-lwc项目的“测试”侧栏中的LWC测试。
测试播放按钮图标。
Play按钮

单击Play按钮 以在一个项目中运行一个或多个测试。将光标悬停在目录或单个测试上以显示播放按钮。运行测试时,结果将显示在终端中。边栏中的颜色也指示结果。绿色表示通过。蓝色表示未运行,橙色表示已跳过测试,红色表示测试失败。单击侧边栏中的测试将打开文件,然后直接转到该测试。

测试刷新按钮图标。
刷新图标

单击刷新图标 以清除测试结果。

测试文件视图中也有直接控件。

在Visual Studio Code中测试文件视图。
测试播放按钮图标。
Play按钮

单击主工具栏中的播放按钮 以运行文件中的所有测试。您也可以单击 文件中每个测试上方的运行测试以运行该特定测试。

测试手表按钮图标。
监视图标

要在每次保存更改时运行文件中的所有测试,请单击主工具栏中的监视图标 。当您在文件中进行测试时,这是一个很好的选择。

哇!这需要很多。 

好吧,让我们编写一些测试。

 

闪电Web组件测试 – 测试入门

学习目标

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

  • 描述单元测试和端到端测试之间的目的和区别。
  • 解释闪电Web组件的单元测试的作用。

先决条件

在本模块中,您将使用Visual Studio代码编辑器在Salesforce DX项目中开发Lightning Web组件和单元测试。如果您不熟悉Lightning Web组件,Salesforce DX或使用Visual Studio Code进行Salesforce开发,我们建议您完成 快速入门:Salesforce DX, 快速入门:Visual Studio Code for Salesforce开发和 快速入门:Lightning Web组件 在继续之前进行项目。

为什么测试很重要

“在设计阶段未检测到的任何错误将在编码阶段花费十倍的时间来进行检测,而在调试阶段将花费十倍的时间。” 

博士 Nikolai Bezroukov,调试的艺术

在瓢虫上搜索放大镜代表的软件错误

调试和测试是相关的,但在软件开发中是不同的过程。测试尝试查找并报告错误。调试尝试找出这些错误的原因并进行修复。根据Nikolai Bezroukov博士的说法,在发现和压缩代码中的错误时,越早越好。

在理想的世界中,软件不会有任何错误。但是现实是,我们会犯错误,需求会被误解,应用程序会以无法预料的方式使用。测试有助于发现这些问题,以便可以解决它们。您发现错误的时间越早,它们就越“便宜”。一旦错误进入开发的下一阶段(或在最坏的情况下为生产),就会有更多的人员和流程参与其中,以进行识别和修复。

对于Salesforce应用程序通常执行两种类型的测试:单元测试和端到端测试。它们的区别在于范围和目的。

单元测试

单元测试的重点是测试应用程序中小的离散功能。为了促进单元测试,请使用可测试的小型单元来构建应用程序,而不是编写单个长的Apex方法或类。这意味着将代码模块化为可以独立测试的离散方法。同样,与其为应用程序编写单个庞大的Lightning组件,不如将功能模块化为较小的组件,这些组件可以独立进行测试。易于运行的简短,快速的单元测试鼓励开发人员在其开发和持续集成过程中编写和运行它们。这样可以确保尽快发现并修复错误。请务必查看 测试驱动开发(TDD), 以更深入地了解此过程。

端到端测试

端到端测试专注于测试整个应用程序或用户旅程。对于Web应用程序,这通常涉及在浏览器中进行测试,以验证页面上的代码和组件在测试环境(例如沙箱或草稿组织)中如何协同工作。

端到端测试往往比单元测试慢,因为它们在每个测试中覆盖了应用程序的更多功能。由于实时环境的随机不一致(例如网络延迟,缓存,对第三方系统的依赖性,基础结构问题等),端到端测试的可靠性也低于单元测试。这些不一致会导致测试一次通过,而下一次失败,这称为拍打测试。尽管存在这些缺点,但是与单元测试相比,端到端测试为应用程序及其集成点提供了有价值的,更实际的验证。

单元测试与端到端测试

让我们看一下单元测试和端到端测试在实践中如何工作。作为示例,我们将使用 lwc-recipes存储 库中 的api- 属性 Lightning Web组件,这 是Salesforce平台上Lightning Web Components的代码示例集合。

<c-api-property>组件由(1)<lightning-card>,(2)<lightning-input>和(3)<c-chart-bar>组件组成。

  1. <lightning-card>组件显示ApiProperty标题,并包含其他两个组件。
  2. <lightning-input>组件处理用户的数字输入并广播值更改事件。
  3. <c-chart-bar>组件根据其百分比值呈现条形图。
API属性组件及其子组件突出显示

这三个组件中的每一个都有其自己的公共API,内部状态和行为。这些组件中的每个组件都可以具有自己的单元测试,以与其他组件隔离来验证其功能。事实上,对于在单元测试<c-api-property>组件可以假设<lightning-card><lightning-input><c-chart-bar>如预期的组件将执行,或者它可以模拟它们的行为在各种条件下,以模拟不同的方案。

在此示例中,端到端测试会将<c-api-property>组件加载到浏览器页面中,在输入字段中输入百分比值,并断言相应的条形图渲染。作为端到端测试,没有任何数据或行为的模拟—您正在确认所有三个组件如何协同工作,就像它们部署到用户时一样。

摘要

下表是单元测试和端到端测试的优缺点的高级比较。 

  单元测试 端到端测试
测试运行很快 没有
测试可靠 没有
测试准确无误,可让您识别出确切的问题 没有
测试一次涵盖了应用程序的许多功能 没有
模拟真实用户 没有

既然您对单元测试和端到端测试之间的区别有了更多的了解,让我们看看它是如何在实践中完成的。本模块的其余部分着重于对Lightning Web组件进行单元测试。