
软件测试对于确保应用程序按预期运行至关重要,尤其是在引入变更时。在开发早期捕捉并修复错误对于保持代码的弹性和高质量至关重要。
在众多可用的 JavaScript 测试工具和框架中,Jest 是最受欢迎的工具和框架之一。作为 Meta 的产品,Jest 为 JavaScript 应用程序和使用 JavaScript 框架构建的应用程序提供了广泛的测试功能。
让我们来探讨一下 Jest 框架、它的功能以及如何最好地将它集成到您的开发工作流程中。
什么是 Jest?
Jest 是一个灵活易用的框架。除了核心的 JavaScript 测试功能外,它还提供配置和插件,以支持测试 Babel、webpack、Vite、Parcel 或基于 TypeScript 的应用程序。
Jest 已被开发人员广泛采用,并拥有一系列社区构建和维护的插件。它的突出特点是易于使用: JavaScript 测试不需要额外的配置或插件。但您也可以使用一些额外的配置选项执行更高级的测试,如测试 JavaScript 框架。
如何为 JavaScript 项目设置 Jest
让我们来探讨如何在现有 JavaScript 项目中设置 Jest。
前提条件
要学习本教程,请确保您已具备以下条件:
- 已安装 Node.js。
- 已安装 npm(Node.js 的一部分)或 Yarn。
- 已安装 Jest npm 软件包。
安装 Jest 软件包
1. 如果您还没有跟进本教程的项目,请将此仓库作为起点。
starter-files 分支为您提供了一个基础,以便在学习本教程的过程中构建应用程序。参考主分支查看本教程的代码,并交叉检查你的代码。
2. 要使用 npm 安装 Jest,请在终端中进入项目目录并运行以下命令:
npm install --save-dev jest
--save-dev 选项会告诉npm将软件包安装到 devDependencies 下,其中包含开发所需的依赖项。
配置 Jest
在 package.json 中配置 Jest
在 package.json 文件中,添加一个名为 jest 的对象,其属性如下所示:
{
…
"jest": {
"displayName": "Ecommerce",
"globals": {
"PROJECT_NAME": "Ecommerce TD"
},
"bail": 20,
"verbose": true
},
}
测试期间,Jest 会搜索该对象并应用这些配置。您可以在 Jest 的配置页面查看其他选项,但该对象的属性包括
displayName— Jest 将此属性值作为标签添加到测试结果中。globals— 保存一个对象值,用于定义测试环境中可用的全局变量。bail— 默认情况下,Jest 会运行所有测试并在结果中显示错误。bail会告诉 Jest 在设定的失败次数后停止运行。verbose— 设置为true时,会在测试执行过程中显示单个测试报告。
在配置文件中配置 Jest
您还可以在 jest.config.js 文件中配置 Jest。Jest 还支持 .ts、.mjs、.cjs 和 .json 扩展名。执行测试时,Jest 会查找这些文件,并应用找到的文件中的设置。
例如,请看这个 jest.config.js 文件:
const config = {
displayName: "Ecommerce",
globals: {
"PROJECT_NAME": "Ecommerce TD"
},
bail: 20,
verbose: true
}
module.exports = config;
该代码导出了一个 Jest 配置对象,其属性与上一示例相同。
你也可以使用一个包含可序列化 JSON 配置对象的自定义文件,并在执行测试时将文件路径传递给 --config 选项。
创建基本测试文件
配置好 Jest 后,就可以创建测试文件了。Jest 会审查项目的测试文件,执行它们并提供结果。测试文件通常采用 [name].test.js 或 [name]-test.js 等格式。这种模式便于 Jest 和你的团队识别测试文件。
请看包含以下代码的 string-format.js 文件:
function truncate(
str,
count,
withEllipsis = true
) {
if (str.length < = count)
return str
const substring = str.substr(0, count)
if (!withEllipsis)
return substring
return substring + '...'
}
module.exports = { truncate }
函数 truncate() 将字符串截断到特定长度,并可选择添加省略号。
编写测试
1. 创建一个名为 string-format.test.js 的测试文件。
2. 为使文件井然有序,请将 string-format.test.js 文件放置在与 string-format.js 文件相同的目录下,或放置在特定的测试目录下。无论测试文件在项目的哪个位置,Jest 都能找到并执行它。使用 Jest,您可以在各种情况下测试应用程序。
3. 在 string-format.test.js 中编写一个基本测试,如下所示:
const { truncate } = require('./string-format')
test('truncates a string correctly', () = > {
expect(truncate("I am going home", 6)).toBe('I am g...')
})
测试用例描述正确截断了一个字符串 truncates a string correctly。这段代码使用了 Jest 提供的 expect 函数,该函数用于测试值是否与预期结果相匹配。
代码将 truncate("I am going home", 6) 作为参数传递给 expect 。这段代码将测试使用参数 "I am going home" 和 6 调用 truncate 所返回的值。 expect 调用会返回一个 expectation 对象,它提供了对 Jest 匹配的访问。
它还包含以 "I am g..." 为参数的 toBe 匹配器。 toBe 匹配器测试期望值和实际值是否相等。
执行测试
要执行测试,请定义 jest 命令。
1. 在项目的 package.json 文件中,添加此 test 脚本:
"scripts": {
"test": "jest"
}
2. 现在在终端运行 npm run test 、 npm test 或 npm t 。它将运行项目的 Jest。
执行测试后,结果就是这样:

string-format.test.js 的 Jest 测试结果成功。
结果显示了一个测试套件(string-format.test.js 文件)、一个成功执行的测试("truncates a string correctly")以及在配置中定义的 displayName (Ecommerce)。
3. 在 string-format.js 中,如果添加一个额外的句点来破坏代码并运行测试,则会失败:

由于截断函数被破坏,Jest 测试结果失败。
这一结果表明你破坏了 truncate 函数或进行了更新,需要更新测试。
如何使用 Jest 编写测试
Jest 测试语法
Jest 的专有语法简单易用。Jest 为您的项目提供全局方法和对象,用于编写测试。它的一些基本术语包括: describe (描述)、 test (测试)、 expect (期望)和 matchers(匹配器)。
describe: 该函数将相关测试组合到一个文件中。test: 该函数用于运行测试。这是it的别名。它包含要测试的值的断言。expect: 该函数为各种值声明断言。它为各种形式的断言提供了匹配器。- Matchers: 它们可以让你以各种方式断言值。你可以断言值相等、布尔相等和上下文相等(如数组是否包含该值)。
要使用匹配器,请参考下面的示例:
1. 用以下代码替换 string-format.test.js 文件中的测试:
describe("all string formats work as expected", () = > {
test("truncates a string correctly", () = > {
expect(
truncate("I am going home", 6)
).toBe("I am g...")
})
})
2. 运行代码。
结果如下:

成功的 Jest 测试结果显示了描述标签。
截图显示, describe 函数中的标签创建了一个块。尽管 describe 是可选项,但将测试分组到一个文件中并提供更多上下文信息还是很有帮助的。
在测试套件中组织测试
在 Jest 中,测试用例由 test 测试函数、 expect 期望函数和匹配器组成。相关测试用例的集合就是测试套件。在前面的示例中,string-format.test.js 就是一个测试套件,由一个测试用例组成,用于测试 string-format.js 文件。
假设项目中还有更多文件,如 file-operations.js、api-logger.js 和 number-format.js。您可以为这些文件创建测试套件,如 file-operations.test.js、api-logger.test.js 和 number-format.test.js。
使用 Jest 匹配器编写简单断言
我们已经探讨了使用 toBe 匹配器的示例。使用其他 Jest 匹配器的断言包括:
toEqual— 用于测试对象实例中的 “深度” 等价性。toBeTruthy— 用于测试一个布尔值是否为真。toBeFalsy— 用于测试一个布尔值是否为假。toContain— 用于测试数组是否包含一个值。toThrow— 用于测试调用的函数是否抛出错误。stringContaining— 用于测试字符串是否包含子串。
让我们来看看使用其中一些匹配器的示例。
例如,您可能希望函数或代码返回一个具有特定属性和值的对象。
1. 请使用下面的代码片段测试这一功能。在这种情况下,您需要断言返回的对象等于预期对象。
expect({
name: "Joe",
age: 40
}).toBe({
name: "Joe",
age: 40
})
本例使用 toBe 。测试失败的原因是这个匹配器没有检查深度相等–它检查的是值,而不是所有属性。
2. 使用 toEqual 匹配器检查深度相等:
expect({
name: "Joe",
age: 40
}).toEqual({
name: "Joe",
age: 40
})
这个测试通过了,因为两个对象都 “深度相等”,也就是说它们的所有属性都相等。
3. 试试另一个匹配器示例,测试定义的数组是否包含特定元素。
expect(["orange", "pear", "apple"]).toContain("mango")
这个测试失败的原因是 toContain 断言 [ "orange" 、 "pear" 、 "apple" ] 数组包含预期值 "mango" ,但数组并不包含。
4. 使用变量进行与下面代码相同的测试:
const fruits = ["orange", "pear", "apple"]; const expectedFruit = "mango"; expect(fruits).toContain(expectedFruit)
测试异步代码
到目前为止,我们已经测试了同步代码–在代码执行下一行之前返回值的表达式。您也可以使用 Jest 的 async 、 await 或 Promises 来处理异步代码。
例如,apis.js 文件中有一个用于发出 API 请求的函数:
function getTodos() {
return fetch('https://jsonplaceholder.typicode.com/todos/1')
}
getTodos 函数向 https://jsonplaceholder.typicode.com/todos/1 发送 GET 请求。
1. 用以下代码创建名为 apis.test.js 的文件,以测试伪造的 API:
const { getTodos } = require('./apis')
test("gets a todo object with the right properties", () = > {
return getTodos()
.then((response) = > {
return response.json()
})
.then((data) = > {
expect(data).toHaveProperty('userId')
expect(data).toHaveProperty('id')
expect(data).toHaveProperty('title')
expect(data).toHaveProperty('completed')
expect(data).toHaveProperty('description')
})
})
该测试用例调用了 getTodos 函数,以获取一个 todo 对象。解析 Promise 时,会使用 .then 方法获取解析后的值。
在该值中,代码返回 response.json() ,这是另一个将响应转换为 JSON 格式的 Promise。另一个 .then 方法会获取包含 expect 和匹配器的 JSON 对象。代码断言 JSON 对象包含五个属性: userId 、 id 、 title 、 completed 和 description。
2. 执行测试:

显示异步代码测试失败的 Jest 测试结果。
如截图所示, getTodos() 测试失败。它期望得到 description 属性,但 API 并没有返回它。有了这些信息,您就可以要求公司的 API 管理团队在应用程序需要时加入该属性,或者更新测试以满足 API 的响应。
3. 删除 description 属性的断言并重新运行测试:

Jest 测试结果显示异步代码测试通过。
截图显示一切都通过了测试。
4. 现在尝试使用 async/await 代替传统的 Promise 处理:
test("gets a todo object with the right properties", async () = > {
const response = await getTodos()
const data = await response.json()
expect(data).toHaveProperty("userId")
expect(data).toHaveProperty("id")
expect(data).toHaveProperty("title")
expect(data).toHaveProperty("completed")
})
现在, async 关键字位于函数之前。代码在 getTodos() 之前使用了 await ,在 response.json() 之前使用了 await 。
高级 Jest 功能
模拟函数和模块
在编写测试时,您可能希望测试一个具有外部依赖性的表达式。在某些情况下,尤其是单元测试,您的单元测试应与外部效应隔离。在这种情况下,您可以用 Jest 模拟您的函数或模块,以便更好地控制测试。
1. 例如,请看包含以下代码的 functions.js 文件:
function multipleCalls(count, callback) {
if (count < 0) return;
for (let counter = 1; counter <= count; counter++) {
callback()
}
}
multipleCalls 函数根据 count 的值执行。它取决于回调函数–外部依赖关系。其目的是了解 multipleCalls 是否正确执行了外部依赖关系。
2. 要在测试文件 functions.test.js 中模拟外部依赖关系并跟踪依赖关系的状态,请使用以下代码:
const { multipleCalls } = require('./functions')
test("functions are called multiple times correctly", () => {
const mockFunction = jest.fn()
multipleCalls(5, mockFunction)
expect(
mockFunction.mock.calls.length
).toBe(5)
})
在这里, jest 对象的 fn 方法创建了一个模拟函数。然后,代码将 5 和 mock 函数作为参数传递,执行 multipleCalls 。然后,断言 mockFunction 被调用了 5 次。 mock 属性包含代码如何调用函数和返回值的信息。
3. 运行测试时,这就是预期结果:

使用模拟函数的成功 Jest 测试结果。
如图所示,代码调用了五次 mockFunction 。
在代码中,mock 函数模仿了外部依赖关系。当应用程序在生产中使用 multipleCalls 时,外部依赖关系是什么并不重要。单元测试并不关心外部依赖关系是如何工作的。它只是验证 multipleCalls 是否按预期运行。
4. 要模拟模块,请使用 mock 方法并传递一个文件路径,即模块:
const {
truncate,
} = require("./string-format")
jest.mock("./string-format.js")
这段代码模仿 string-format.js 输出的所有函数,并跟踪其调用频率。模块的 truncate 变成了一个 mock 函数,这会导致函数失去其原始逻辑。你可以从 truncate.mock.calls.length 属性中了解测试中 truncate 的执行次数。
如果出现错误或代码无法运行,请将代码与完整的实现进行比较。
使用 Jest 和 React 测试库测试 React 组件
如果您还没有项目来跟进本教程,可以使用 React 示例项目作为起点。 starter-files 分支可以帮助您在学习本教程的过程中开始编写代码。将主分支作为参考,对照本教程的完整代码检查您的代码。
您可以使用 Jest 测试 React 等 JavaScript 框架。当你使用 Create React App 创建 React 项目时,它们会立即支持 React 测试库和 Jest。如果不使用 Create React App 创建 React 项目,则需要安装 Jest,以便使用 Babel 和 React 测试库测试 React。如果克隆 starter-app 分支,则无需安装依赖项或应用配置。
1. 如果使用示例项目,请使用此命令安装所需的依赖项:
npm install --save-dev babel-jest @babel/preset-env @babel/preset-react react-testing-library
您也可以使用 Enzyme 代替 React Testing Library。
2. 更新 babel.config.js 中的 Babel 配置,如果该文件不存在,则创建该文件:
module.exports = {
presets: [
'@babel/preset-env',
['@babel/preset-react', {runtime: 'automatic'}],
],
};
3. 请看包含以下代码的 src/SubmitButton.js 文件:
import React, { useState } from 'react'
export default function SubmitButton(props) {
const {id, label, onSubmit} = props
const [isLoading, setisLoading] = useState(false)
const submit = () => {
setisLoading(true)
onSubmit()
}
return
该 SubmitButton 组件接收三个道具:
id— 按钮的标识符。label— 要在按钮中显示的文本。onSubmit— 当有人点击按钮时触发的函数。
代码将 id 属性分配给 data-testid 属性,该属性用于标识测试元素。
该组件还会跟踪 isLoading 状态,并在有人点击按钮时将其更新为 true 。
4. 为该组件创建测试。将以下代码放入 SubmitButton.test.js 文件中:
import {fireEvent, render, screen} from "@testing-library/react"
import "@testing-library/jest-dom"
import SubmitButton from "./SubmitButton"
test("SubmitButton becomes disabled after click", () => {
const submitMock = jest.fn()
render(
)
expect(screen.getByTestId("submit-details")).not.toBeDisabled()
fireEvent.submit(screen.getByTestId("submit-details"))
expect(screen.getByTestId("submit-details")).toBeDisabled()
})
上面的代码渲染了 SubmitButton 组件,并使用 screen.getByTestId 查询方法通过 data-testid 属性获取 DOM 节点。
第一个 expect 是 getByTestId("submit-details") ,并使用 not 修饰符和 toBeDisabled 匹配器(从 react-testing-library 暴露)来断言按钮未被禁用。在每个匹配器中都使用 not 修饰符,以断言匹配器的对立面。
然后,代码会触发组件上的 submit 事件,并检查按钮是否禁用。你可以在测试库文档中找到更多自定义匹配器。
5. 现在,运行测试。如果克隆了 starter-files 分支,请在开始测试前运行 npm install ,确保安装了所有项目依赖项。

显示 react 组件测试通过的 Jest 测试结果。
运行代码覆盖率报告
Jest 还提供代码覆盖率报告,可显示项目的测试范围。
1. 向 Jest 传递 --coverage 选项。在 package.json 中(JavaScript 项目中)的 Jest 脚本中,使用此覆盖率选项更新 Jest 命令:
"scripts": {
"test": "jest --coverage"
}
2. 运行 npm run test 测试代码。你会得到如下报告:

每个测试服的成功 Jest 覆盖率报告。
该报告显示,Jest 已 100% 测试了 SubmitButton.js 和 string-format.js 中的函数。它还显示 Jest 未测试 string-format.js 中的任何语句和行。测试覆盖率显示,string-format.js 中未覆盖的行是第 7 行和第 12 行。
在第 7 行, truncate 函数中的 return str 没有执行,因为 if (str.length <= count) 条件返回 false。
在第 12 行,同样是在 truncate 函数中,return substring 不执行,因为 if (!withEllipsis) 条件返回 false。
将 Jest 与您的开发工作流程相结合
让我们看看如何集成这些测试来改进开发工作流程。
在观察模式下运行测试
你可以在更改代码时使用观察模式自动运行测试,而不是手动执行测试。
1. 要启用观察模式,请更新 package.json(JavaScript 项目)中的 Jest 命令脚本,添加 --watchAll 选项:
"scripts": {
"test": "jest --coverage --watchAll"
}
2. 运行 npm run test 。它将在观察模式下触发 Jest:

以观察模式运行 Jest
每次更改项目时都会运行测试。这种方法有助于在构建应用程序时获得持续反馈。
设置预提交钩子
在 Git 环境中,只要发生特定事件(如拉、推或提交),钩子就会执行脚本。预提交钩子定义了哪些脚本会在预提交事件(代码在提交前触发的事件)中运行。
只有脚本不出错,提交才会成功。
在预提交前运行 Jest 可确保在提交前没有任何测试失败。
您可以使用各种库在项目中设置 git 挂钩,例如 ghooks。
1. 在 devDependencies 下安装 ghooks:
npm install ghooks --save-dev
2. 在 package.json 文件(JavaScript 项目)的顶层添加 configs 对象。
3. 在 configs 下添加一个 ghooks 对象。
4. 添加一个关键字为 pre-commit 、值为 jest 的属性。
{
…
"config": {
"ghooks": {
"pre-commit": "jest"
}
},
}
5. 提交代码 代码会触发预提交钩子,执行 Jest:

使用 ghooks 在预提交期间运行 Jest。
小结
现在您知道如何将 Jest 集成到您的开发工作流中,以便在您进行更改时自动执行。这种方法可提供持续反馈,因此您可以在将更改发布到生产环境之前快速修复任何代码问题。















暂无评论内容