Salesforce开发人员的JavaScript技能-了解浏览器中的JavaScript

学习目标

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

  • 描述JavaScript和浏览器之间的交互。
  • 解释Web API的角色。
  • 使用基本的文档对象模型API。
  • 描述闪电组件抽象DOM的方式。

JavaScript在浏览器中的工作方式

您刚刚了解了一些JavaScript运行时引擎和语言。您还学到了一些编写JavaScript的人都应该知道的东西。在本模块中,我们重点介绍JavaScript与浏览器之间的关系,特别关注您的网页在JavaScript中的外观。 

JavaScript运行时不是浏览器,也不是浏览器JavaScript

因此,JavaScript运行时引擎可以在许多不同的地方运行,但是最常托管在浏览器中。那么JavaScript和浏览器之间有什么区别? 

浏览器的主要工作是充当Web服务器的客户端。它使用几种协议(通常为HTTP / HTTPS)之一通过Internet请求资源。服务器将其中的一些资源传递给服务器后,浏览器需要对其进行处理。至少将HTML和CSS渲染到页面中。当资源包含一些JavaScript时,浏览器将到达JavaScript运行时引擎以解析,评估和执行该代码。 

同样,在执行脚本时,它也可以返回到浏览器以执行诸如修改网页,与本地环境交互或与其他Web资源交互的操作。

具有API的JavaScript引擎

为了使这些交互有效,浏览器会显示API。实际上,人们认为客户端JavaScript的很大一部分实际上就是这些API。 

Web API

标准品

能够与浏览器进行交互的API并不奇怪。就像任何其他编程语言或平台界面API与其在其上运行的环境进行交互一样,您的浏览器也是如此。 

虽然超过一半的浏览器互联网 流量来自Google Chrome浏览器,但大约另外30%来自其他五个浏览器。这强调了Web标准对于这些API的重要性,因此JavaScript可以编写一次并可以在任何地方运行。 

将Web API添加到图片

以前,我们创建了JavaScript运行时的图片。 

  • 请求使用单个线程在堆栈上执行。
  • 一个请求将保留线程,直到该线程执行完该请求的所有同步逻辑为止。
  • 新请求将排队,直到线程准备就绪。
  • 事件循环在下一个排队的请求可用时将其移入堆栈。

现在,我们将Web API添加到JavaScript图片中。这些扩展了核心JavaScript语言。它们显示界面,这些界面执行了许多对于现代Web用户体验至关重要的工作。例如,浏览器API可以: 

  • 与浏览器中呈现的当前页面的结构进行交互(文档对象模型或DOM API)
  • 在不离开当前页面的情况下执行对服务器的异步请求(Fetch API)
  • 与音频,视频和图形进行交互
  • 与显示在浏览器上的设备功能进行交互(地理位置,设备方向,客户端数据存储)

在大多数情况下,正是这些API向队列添加了新请求。 

注意

注意

我们正在避开JavaScript生态系统的重要组成部分:抽象或扩展浏览器功能的第三方API和JavaScript库。这些在野外使用JavaScript方面起着重要作用。 

就我们在​​Salesforce中的目的而​​言,我们涵盖了我们的JavaScript框架:Lightning Component Framework。具体来说,当谈论Lightning Platform上的JavaScript时,此模块介绍了Lightning Web组件开发模型。

举个例子。您在带有表单的网页上有一个注释列表。您填写表单上的某些字段,单击按钮以保存该数据,然后将新项添加到列表中。为了好玩,我们还说您将在本地缓存此新记录,以加快涉及该记录的将来请求。 

这个简单的用例将与以下浏览器API交互

  • 提取API(以保存记录)
  • DOM API(用于将新项目添加到HTML列表中)
  • 客户端数据存储(用于本地缓存数据)

让我们深入了解DOM API,因为它代表了用户在JavaScript中看到的内容。 

文档对象模型

DOM:您的JavaScript页面

当请求页面然后由浏览器接收时,浏览器将解析HTML并创建该页面的描述或模型。此模型用于在浏览器的视口中绘制页面。它也通过DOM浮出水面。 

将DOM视为一棵树。它始于浏览器显示功能的根:窗口。从那里开始,页面被封装在中window.document,页面的主体位于window.document.body。然后,树会扇动到页面上所表示的每一位内容。大多数网页都有一个非常复杂的树,其中许多节点最终以叶节点作为层次结构中最细化的部分结束。 

作为一个API,DOM是巨大的,它使您可以触摸树的每个部分。它还具有许多优化与DOM交互的方法。看一下jsbin.com 上的这个简单 示例。它 包括:

  • 输入字段
  • 添加项目按钮
  • 无序列表

每次单击按钮时,代码都会到达输入字段,读取其值,将其转换为文本节点,创建一个新li元素,将文本节点插入该li节点,最后将新的li-and-text节点粘贴到的ul。 

注意

为了获得更多乐趣,并且如果您想实践到目前为止讨论的一些原理,请查看是否可以修改jsbin以执行以下操作。

  • 检查文本输入中是否有数据
  • 如果不是,请不要在列表中添加新的li
  • 添加新li后,清除文本输入

如果悬念杀死了您,请查看修改后的 代码。

如您所见,仅此简单示例就需要开发人员执行一系列手动步骤。而且它甚至不会在服务器上存储任何数据,也不会以任何其他方式与服务器交互。因此,JavaScript库(reactjs,jQuery)和框架(Angular,vuejs)已成为交互式页面的标准。这样的框架会抽象化并简化DOM交互,并且通常会自动对缺少的功能应用polyfill。闪电组件框架没有什么不同。 

使用Shadow DOM封装

DOM API丰富而灵活。使用相对简单的JavaScript,很容易对UI调用的外观,行为和动作进行更改。 

但是有一个陷阱。DOM模型使得难以封装UI部分并保护它们免受意外(或有目的和恶意)更改的影响。 

因此,开发了Shadow DOM标准。Shadow DOM在UI功能的特定部分周围创建边界。该边界可防止父母更改孩子的元素或CSS。它还会强制跨边界传播的所有事件重新确定其目标范围,从而阻止父级跨越阴影DOM边界。

Lightning组件框架:Aura组件和Lightning Web组件

从Spring ’19版本(API版本45.0)开始,您可以使用两个编程模型来构建Lightning组件:Lightning Web组件模型和原始的Aura组件模型。闪电Web组件是使用HTML和现代JavaScript构建的自定义HTML元素。闪电Web组件和Aura组件可以在页面上共存和互操作。此内容仅涵盖Lightning Web Components。

闪电Web组件和DOM

注意

从现在开始,每个单元都有一个相似的部分,并带有一些示例,以说明与Lightning Web Components的关联。如果您想应用所学内容,现在是开始在Trailhead上使用Lightning Web Components Basics 模块的好时机 。

自动DOM更新

Lightning组件的基本价值在于构建由Salesforce数据驱动的自定义UI。这与DOM操作有关,因为DOM本身是由Salesforce数据驱动的。 

该组件以最简单的形式演示了该原理。

具有数据驱动UI的简单组件,显示的文本为“此文本来自JS道具。

这是此组件的代码。

// JavaScript Module: demo.js
import { LightningElement } from 'lwc';
export default class Demo extends LightningElement {
    text = 'This text came from a JS prop';
}
<!-- HTML Template: demo.html -->
<template>
    <lightning-card title="Basic DOM Example" icon-name="utility:hierarchy">
        <div class="slds-card__body slds-card__body-inner">
            <p>
                <lightning-formatted-text value={text}></lightning-formatted-text>
            </p>
        </div>
    </lightning-card>
</template>

首先介绍一下上下文:每个Lightning Web组件都需要一个JavaScript模块。如果组件包含要呈现给用户的任何元素,则也需要HTML模板。大多数组件在捆绑中都包含一个JavaScript和一个HTML文件。 

Lightning Web Components使将数据呈现到UI变得毫不费力。例如,上面的代码使用JavaScript属性text存储硬编码的字符串。在<lightning-formatted-text>标记中,value 属性的text属性绑定到JavaScript text属性,从而在UI中呈现数据。 

但是,如果您希望用户交互时更改UI,该怎么办?我们可以扩展这个例子来做到这一点。

这是代码。

// JavaScript Module demo2.js
import { LightningElement, track } from 'lwc';
export default class Demo2 extends LightningElement {
    @track text = '';
    handleChange(event){
        this.text = event.target.value;
     }
}
<!-- HTML Template demo2.html -->
<template>
    <lightning-card title="Basic DOM Example" icon-name="utility:hierarchy">
        <div class="slds-card__body slds-card__body-inner">
            <p>
                <lightning-formatted-text value={text}></lightning-formatted-text>
            </p>
            <p>
                <lightning-input onchange={handleChange} label="Input Text" value={text}></lightning-input>
            </p>
        </div>
    </lightning-card>
</template>

如果text再次查看该属性,则会看到我们添加了一些新行为。通过使用@track,我们利用了称为装饰器的相对较新的JavaScript功能。此装饰器将功能附加到属性,以确保在其值更改时重新呈现组件的DOM。  

在模板中,我们添加了一个<lightning-input>。尽管绑定text属性是显示数据的关键,但请注意该onchange属性。handleChange现在已由change事件触发已添加到JavaScript模块的功能。用户对值的每次修改都会更新的值text。文本更新时,DOM也更新。  

尽管这是一个人为的示例,但它说明了Lightning Web Components如何显示使隐式DOM更改易于实现为开发人员的功能。

显式DOM操作

在构建Lightning Web组件时,手动DOM操作不应该是您的首要策略,但是更复杂的组件可能需要您这样做。这是一个更高级的主题,但是在签署DOM之前值得先了解一下它的工作原理。 

有条件的渲染

编写Lightning Web组件时,可以显式定义UI的部分,这些部分仅在满足某些条件时才呈现。您可以通过在嵌套 标记中添加if:trueor if:false指令来实现template。 

//conditionalButton.js
import { LightningElement, track } from 'lwc';
export default class ConditionalButton extends LightningElement {
    @track show = true;
    handleClick(){
        this.show = !this.show;
    }
}
//conditionalButton.html
<template>
    <lightning-card title="Conditional Rendering with Button" icon-name="utility:hierarchy">
        <div class="slds-card__body slds-card__body-inner">
            <p>
                <template if:true={show}>
                    Peek-a-boo!
                </template>
                <template if:false={show}>
                    I'm hiding!
                </template>
            </p>
            <p>
                <lightning-button onclick={handleClick} variant="neutral" label="Switch"></lightning-button>
            </p>
        </div>
    </lightning-card>
</template>

在此代码示例中,文本Peek-a-boo!show属性值为时出现true。当show设置为时false我隐藏的文本出现。单击该按钮可切换show属性的值。

手动操作DOM的其他方法

在某些情况下,可以使用显示CSS样式来操作DOM元素的显示。尽管此方法在Lightning Web Components中照常工作,但超出了此特定模块的范围。 

Salesforce开发人员的JavaScript技能-了解JavaScript核心概念

学习目标

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

  • 描述JavaScript运行时环境的性质。
  • 区分JavaScript引擎和语言。
  • 学习JavaScript时避免关键陷阱。
  • 描述一些重要的JavaScript最佳实践。

JavaScript的内容,原因和方式

在2000年代初期,软件世界开始爆炸,您可以构建完全通过Internet托管和交付的应用程序。尽管我们现在将Web应用程序视为理所当然,但当时它们就像看到机器人骑自行车一样令人着迷。 

骑自行车的机器人。

浏览器是简单的HTML渲染器,而HTML和JavaScript标准是分散的。要创建具有甚至稍微复杂的功能的网页,您必须强制用户仅使用一种浏览器,通常是Internet Explorer。直到今天,由于这个时代做出的设计决策,仍有一些应用程序强制执行此操作。 

为了解决这些局限性,服务器端UI框架得到了发展,允许软件开发人员在服务器上动态创建网页。在服务器上执行计算能力的复杂逻辑:然后,将渲染的HTML投放到功能弱的浏览器中。在这个世界上,JavaScript主要是一种使页面具有某种程度的交互性并执行基本逻辑而无需服务器跳闸的方法。服务器端框架非常流行,其中包括Salesforce框架Visualforce。为了证明这种受欢迎程度,今天在Salesforce中存在着数百万个Visualforce页面。 

快进当今世界是一个不同的地方。浏览器是功能强大的应用程序,可以优化网页查看。JavaScript已按照ECMAScript标准进行了很好的标准化,并且顶级浏览器制造商通常都擅长采用新功能。今天的Web应用程序具有丰富的用户界面,该用户界面在浏览器中运行现代JavaScript。代替服务器端框架,现代Web应用程序倾向于客户端渲染。在Salesforce中,这是通过Lightning Component Framework完成的。但是,对于许多开发人员而言,编写JavaScript仍然是新事物。如果您主要使用Visualforce页面和Apex控制器,则可能需要全面了解JavaScript的工作原理,以便更好地了解您的组件。 

是时候提高您的JavaScript技能了。 

JavaScript运行时

JavaScript运行时是解释JavaScript代码的引擎。它可以是浏览器或服务器等其他运行时环境的一部分。现代JavaScript引擎功能完善,功能强大,可以优化执行,并符合ECMAScript标准。 

JavaScript引擎的定义功能是由下面的堆栈表示的单线程运行时。在堆栈中完成的工作拥有线程,并且必须先完成其同步逻辑,然后再移交对线程的控制。 

具有堆栈,队列,事件和API的JavaScript引擎

运行时是一个繁忙的地方。可以随时随地从许多来源来进行新工作,包括用户(UI事件)和Web API(例如地理位置或设备移动)。由于只有一个线程,因此有一个队列等待工作轮到使用该线程。 

当堆栈为空时,事件循环从队列中获取等待完成的工作,并将其移入堆栈。 

事件循环从队列中获取工作,并将其传递给堆栈

当然,这是一种简化,但是它说明了JavaScript引擎如何完成工作的基本模型。由于具有这种体系结构,因此JavaScript语言可以像实际一样工作。 

JavaScript语言

作为一种语言,JavaScript经常被误解。如果您将其拾起任何时间,无疑会问自己一些问题。它是脚本语言吗?它与Java有什么关系?它是一种真正的编程语言吗?为什么会做? 

让我们围绕JavaScript作为一种语言来设置一些上下文,以帮助您回答其中的一些问题。 

JavaScript正在改变

因为JavaScript是根据ECMAScript标准构建的,所以它一直在变化。每年都会发布描述新功能的标准更新,然后JavaScript引擎项目(浏览器和运行时制造商)将它们安装到位。 

随着JavaScript的成熟,更新可能包括更多现代语言功能。在其他情况下,添加功能以实现现有功能的更简洁的语法(这些称为语法糖)。 

API的采用并不普遍

这句话看起来很吓人,但是绝大多数JavaScript API都可以在最常见的浏览器平台上使用。尽管如此,实现者并没有以相同的速度采用每种语言功能或API。有些人从不采用某些功能(尽管这种情况很少见)。通常,如果您想使用该语言的较新功能,则应使用caniuse.com之类的资源来了解它在目标浏览器中的 运行情况。 

如果新功能不是本机实现的,那么通常会编写一些代码来暂时实现该缺失功能的目的。此临时填写代码称为polyfill。实际上,Lightning Component Framework使用精选的polyfill列表,这些列表在运行其他代码之前被应用以自动改善浏览器的兼容性。  

必不可少的事情

现在该看一些代码了。让我们从每个JavaScript开发人员应该知道的一些事情开始,使他们的生活更轻松。 

记住区分大小写

JavaScript区分大小写。对于许多习惯于Apex和SOQL不区分大小写的Salesforce开发人员而言,这是棘手的。请记住,每次在Salesforce中编写JavaScript代码时,请注意区分大小写。 

声明变量

变量声明做是使用三个运营商之一:varlet,和const。一般使用letconst。下表总结了这三个关键字之间的功能差异。 

关键词
范围
可变分配
变种
功能




const

没有

范围是我们稍后讨论的主题,因此让我们解决可变性的含义。 

所有变量都是指针。赋值是将变量指向内存中某物的行为。可变性是在初始分配变量后是否可以重新分配变量。使用varlet创建可变指针,而该指针const是不可变的。通常,最好通过演示来理解这一点 。 

注意

JavaScript游乐场是一个有价值的工具,可以试用代码,并在浏览器中实时查看其工作方式。有很多JavaScript游乐场,但是在本模块中,我们将链接到jsbin中的示例 。

//primitive assignments
var myBike = "Mountain Bike";
let currentGear = 5;
const numberOfGears = 12;
//reassignment
myBike = "Penny Farthing"; // this works
currentGear = 1; // so does this
numberOfGears = 1; // error

以上,myBike并且currentGear在重新分配值时没有问题。但是,当尝试这样做时numberOfGears,会出现错误。 

当 工作与对象(而不是原语),请记住,const只有防止重新分配您的变量设置不同的对象。仍然可以更改对象本身(其属性,函数等),如下所示。 

// call constructor, new object, assign it to bike
const bike = new Bike();
//Change internal state by calling a function
bike.changeGear("front", "Up");
// add a new member to bike that did not exist before
bike.type = "Penny Farthing";
// check for success
console.log(bike.calculateGearRatio()); // 4.0909...
console.log(bike.type); // "Penny Farthing"
// attempt to point bike to new instance of Bike
bike = new Bike(1,2); // error

在这里,我们从Bike构造函数创建一个对象,并将其分配给bike变量(记住,区分大小写)。然后,我们可以更改所需的任何内容,例如调用更改其状态甚至添加新成员的函数。但是,当我们尝试将Bike重新分配给其他东西时,在这种情况下,通过再次调用构造函数,就会出现错误。 

隐式强制

当大多数JavaScript运算符遇到无效类型时,它们会尝试将值转换为有效类型。这种隐式转换类型的过程称为隐式类型强制。考虑以下: 

let num1 = 9 * "3";
console.log(num1); // 27 (a number)
let num2 = 9 + "3";
console.log(num2); // "93" (a string)

在第一个示例中,*运算符只能用于数学运算,将字符串强制"3"为数字。结果为27。在第二个+运算符中,该运算符将字符串视为"3"一元运算符(并置)。这将强制9以字符串"9"的结果强制输出到字符串"93"。 

乍一看,这似乎很方便,但实际上可能导致令人困惑的结果。 

不要使用隐式强制

隐式类型强制的许多实例令人困惑。例如布尔比较。C系列语言常用的==  and !=比较运算符将尝试将任何内容转换为布尔值。有确定性的规则,但是它们太复杂而无法实际应用。这是一些有趣的例子。

false == ""; // true
false == "0"; // true
"" == "0"; // false
[0] == 0; // true

“为什么这样做?” 

究竟。

对于布尔比较,最佳实践是使用===!==。使用这些运算符,基本类型仅在类型和值都匹配时才等效,并且对象比较仅在它们各自的指针指向相同的内存地址时才为真。尝试与上述相同的比较: 

false === ""; // false
false === "0"; // false
"" === "0"; // false
[0] === 0; // false

真假

您知道我们刚刚针对隐式类型强制发出警告的规则吗?好吧,这是个例外。 

当表达式期望布尔值时,以下值始终被视为false。 

  • false (当然)
  • 0 (数字零)
  • ""''(一个空字符串)
  • null
  • undefined
  • NaN (数学运算失败的结果)

falsefalse(当然!)。这些值的其余部分被强制为false。作为一个整体,他们被称为虚假。 

毫无疑问,true仅此而已true。但是任何其他类型的值都被强制转换为true。我们称这些价值观是真实的。 

这有一个方便的副作用。假设您要通过简单地将变量传递到if表达式中来测试几种类型的无效数据。 

const myRecord = response.getReturnValue();
if (myRecord) {
  //now process the record
}

如果由于某种原因上述分配失败,并且我们最终得到一个未初始化的变量(undefined),一个空字符串或0,我们仅通过对该myRecord变量进行条件检查就覆盖了所有这些基础。这是JavaScript中广泛接受的做法。 

this 是棘手的

该模块包含有关如何使用this指针的整个章节。它具有定义明确的规则,但是this即使在同一功能内,指向的内容也可以更改。例如,在Apex类中,您可能会看到: 

public class MyApexClass {
  private String subject;
  public MyApexClass(String subject) {
    this.subject  = subject;
  }
}

在此示例Apex类中,this仅引用的当前实例MyApexClass。在JavaScript中,this指向的内容不取决于定义函数的位置,而是取决于该函数的调用位置。更多关于this以后。 

功能就是价值

在JavaScript中,一切都是对象。这也适用于功能。与其他对象一样,可以将函数分配给变量,将其传递给其他函数的参数,并使用与其他任何对象相同的方式。 

如果您未使用函数是一等公民或使用lambda的语言,这可能会有些令人震惊。稍后,我们将整个单元用于功能上。 

此后,还有更多其他事情。 

现在,您已经对JavaScript概念有了很好的基本介绍。接下来,我们来看一下JavaScript在浏览器中的工作方式。

闪电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组件进行单元测试。