如何使用 Jest 在 JavaScript 中编写单元测试

2025-05-25

如何使用 Jest 在 JavaScript 中编写单元测试

单元测试是开发过程中一个重要但经常被忽视的环节。许多人认为它枯燥乏味,而且由于传统上难以正确设置,它在早期就名声不佳。交付高质量代码的好处肯定大于坏处,但如何才能找到时间和精力来开始编写单元测试呢?

对我们来说幸运的是,借助 Jest,用 JavaScript 编写单元测试从未如此快速、简单,而且可以说更有趣。

Jest是一个功能丰富的 JavaScript 测试框架,旨在让测试普及大众。它几乎零配置的设置方式让测试变得简单,而熟悉的 API 也让测试编写变得相当简单。

本文将简要介绍 Jest 及其单元测试背后的概念。我们将学习如何安装 Jest、使用测试用例和 Fixture 编写测试套件,以及如何在有覆盖率报告和无覆盖率报告的情况下运行测试。

我们假设正在测试一个包含简单函数的模块,该函数充当验证规则。该规则检查被验证的值是否为整数。例如:

// isInteger.js
module.exports = (value) => !isNaN(parseInt(value, 10));
Enter fullscreen mode Exit fullscreen mode

这种实现方式是故意设计得幼稚且有缺陷的。我们希望通过测试用例的通过和失败来了解代码中存在的缺陷。本文不涉及如何修复这种实现方式,但欢迎大家在阅读过程中自行尝试。

继续阅读以了解更多信息!


什么是单元测试?

单元测试是对源代码单元的自动化测试。单元测试断言单元的行为是否符合预期。

单元通常是一行代码、一个函数或一个类。单元的构成并没有严格的定义,但通常从看起来“最小”的开始。

没有依赖关系的单元称为孤立(孤独)单元。有依赖关系的单元称为群居单元

独立单元易于测试,但社交单元则更难测试。社交单元的输出依赖于其他代码单元——如果其他单元失败,被测试的单元也会失败。因此,产生了两种单元测试风格:社交单元测试和独立单元测试。

如果社交单元的依赖项也失败,社交单元测试也会失败。如果被测试单元的依赖项无法正常工作,那么它就不应该工作,所以在这种情况下,测试失败是一个好兆头。

独立单元测试通过创建依赖项的模拟实现来隔离社交单元。模拟实现控制依赖项在测试期间的行为,使社交单元的测试更具可预测性。

无论单元测试风格如何,单元测试的目标都是一样的——确保程序的各个部分按照预期正常工作


什么是 Jest?

Jest是一个 JavaScript 测试框架,旨在让测试尽可能简单。它在一个包中提供了运行测试、创建断言、模拟实现等所有必需的工具。

在 Jest 出现之前,JavaScript 生态系统依赖于多种不同的工具和框架来为开发者提供编写和运行测试的方法。配置这些工具并非易事。Jest 旨在通过使用合理的默认配置来解决这个问题,这些配置“开箱即用”,在大多数情况下几乎不需要任何额外的配置。

Jest 是目前最受欢迎的测试技术之一,自 2017 年以来在 JS 开发者现状调查中持续获得高满意度评分。它是测试 JavaScript 项目的可靠选择。

💡注意
Jest 还通过 Babel支持TypeScript。


如何安装 Jest?

使用您选择的包管理器将jest (和可选的类型)安装到新的或现有的项目文件中:package.json

# For NPM users
npm install --save-dev jest @types/jest

# Yarn users
yarn add --dev jest @types/jest
Enter fullscreen mode Exit fullscreen mode

就这样!我们现在可以使用 Jest 运行测试了。

💡注意:
将 Jest 和其他测试工具作为开发依赖项进行安装是一种很好的做法。在仅安装项目构建和运行所需依赖项的环境中,这可以加快安装速度。


如何使用 Jest 运行测试?

要使用 Jest 运行测试,请调用项目文件夹根目录中的jest 命令。

我们将使用为我们package.json调用命令的测试脚本来更新项目:jest

{
    // ... package.json contents
    "scripts": {
        // ... existing scripts
        "test": "jest"
    }
}
Enter fullscreen mode Exit fullscreen mode

我们现在可以运行新创建的test脚本:

# NPM users
npm run test

# Yarn users
yarn run test
Enter fullscreen mode Exit fullscreen mode

如果一切设置正确,Jest 将向我们提供它发现和运行的任何测试的结果。

💡注意:
当测试用例失败时,Jest 将以状态码 1 退出。npm ERR!在这种情况下,控制台中应该会显示错误。


如何使用 Jest 创建测试?

为了创建用于 Jest 的测试,我们创建了一个包含测试用例的文件*.spec.js*.test.js

💡注意
Jest默认配置为在文件夹内查找.js.jsx文件,以及任何带有或后缀.ts的文件(这包括名为或 的文件)。.tsx__tests__.test.spectestspec

由于isInteger.js是我们正在测试的模块的名称,我们将在isInteger.spec.js与模块同一文件夹中创建的文件中编写测试:

// isInteger.spec.js
test("Sanity check", () => {
    expect(true).toBe(true);
});
Enter fullscreen mode Exit fullscreen mode

💡注意:
无论您选择在专用文件夹中还是在模块旁边编写测试,在项目内部构建测试都没有正确或错误的方法。Jest 足够灵活,无需配置即可适用于大多数项目架构。

该测试的描述是“健全性检查”。健全性检查是确保系统行为合理性的基本测试。该测试将断言我们预期的true值为true

运行测试,如果通过,则一切设置正确。

恭喜!我们刚刚完成了第一个测试!


如何在 Jest 中编写测试用例?

要编写测试用例,我们首先定义必须验证的结果,以确保系统正常运行。

isInteger.js模块是一个函数,它接受一个参数,并返回true该参数是否为整数值false。我们可以基于该定义创建两个测试用例:

  1. isInteger()传递整数值;
  2. isInteger()非整数值导致失败。

要在 Jest 中创建测试用例,我们使用test() 函数。它将测试名称字符串和处理函数作为前两个参数。

💡注意:
test()函数也可以使用别名 - 来调用it()。请根据可读性或个人喜好选择其中一种。

测试基于断言。断言由期望值匹配器组成。最简单、最常见的断言期望测试值与特定值匹配。

expect() 使用函数创建一个期望。它返回一个匹配器方法的对象,我们通过该对象断言测试值的预期值。匹配器方法 toBe()检查期望值是否与给定值匹配。

在我们的测试中,我们可以预期整数isInteger()值为true1,false非整数值为 1.23。

// isInteger.spec.js
const isInteger = require("./isInteger");

test("isInteger passes for integer value", () => {
    expect(isInteger(1)).toBe(true);
});

test("isInteger fails for non-integer value", () => {
    expect(isInteger(1.23)).toBe(false);
});
Enter fullscreen mode Exit fullscreen mode

运行 Jest 现在应该向我们提供哪些测试通过、哪些测试失败的报告。


如何在 Jest 中使用装置?

要在 Jest 中使用 Fixture,我们可以使用test.each() 函数。它对 Fixture 数组中的每个 Fixture 执行测试。

Fixture是表示条件(例如函数参数和返回值)的数据,单元测试就是在这些条件下执行的。使用 Fixture 是一种快速简便的方法,可以断言单元的行为在不同条件下是否符合预期,而无需编写多个测试。

在 Jest 中,fixture 可以是单个值或值数组。fixture 可以通过参数在测试处理函数中使用。fixture 的值可以通过printf 格式注入到描述中。

// isInteger.spec.js
const isInteger = require("./isInteger");

const integerNumbers = [-1, 0, 1];

test.each(integerNumbers)(
    "isInteger passes for integer value %j",
    (fixture) => expect(isInteger(fixture)).toBe(true)
);

// ... or...
const integerNumbers = [
  [-1, true],
  [-0, true],
  [1, true]
];

test.each(integerNumbers)(
    "isInteger passes for integer value %j with result %j",
    (fixture, result) => expect(isInteger(fixture)).toBe(result)
);
Enter fullscreen mode Exit fullscreen mode

运行 Jest 现在应该向我们提供一份关于哪些测试通过、哪些测试失败的报告,其中每个测试都对应着我们装置数组中的一个装置。

💡 Note
%j是一个printf 格式说明符,用于将值打印为 JSON。对于包含不同类型值的装置来说,这是一个不错的选择。


如何将 Jest 中的测试用例分组到测试套件中?

要将 Jest 中的测试用例分组到测试套件中,我们可以使用describe() 函数。它以套件名称字符串和处理函数作为前两个参数。

测试套件是为执行目的而组合在一起的测试用例的集合。测试套件的目标是根据常见的行为或功能来组织测试。如果套件中的所有测试都通过,我们就可以认为测试的行为或功能符合预期。

// isInteger.spec.js
const isInteger = require("./isInteger");

describe("isInteger", () => {
    const integerNumbers = [-10, -1, 0, 1, 10];

    test.each(integerNumbers)(
        "passes for integer value %j",
        (fixture) => expect(isInteger(fixture)).toBe(true)
    );

    const floatNumbers = [-10.1, -1.1, 0.1, 1.1, 10.1];

    test.each(floatNumbers)(
        "fails for non-integer value %j",
        (fixture) => expect(isInteger(fixture)).toBe(false)
    );
});
Enter fullscreen mode Exit fullscreen mode

运行 Jest 现在应该向我们提供一份报告,说明哪些测试通过,哪些测试失败,并分组到描述的测试套件中。

💡注释
describe()块也可以嵌套以创建更复杂的测试层次结构。


每次文件更改时如何运行 Jest?

要在每次文件更改时运行 Jest,我们可以使用--watch--watchAll 标志

--watch标志将告诉 Jest 监视 Git 跟踪的文件的更改。Jest 将仅运行受更改文件影响的测试。要使此功能正常工作,该项目还必须是 Git 存储库。

--watchAll标志将告诉 Jest 监视所有文件的更改。每当文件发生更改时,Jest 将运行所有测试。

--watch和模式--watchAll支持在测试运行时对测试进行额外的过滤。这样就可以只运行与文件名匹配的测试,或者只运行失败的测试。

# Runs tests on changed files only and re-runs for any new change
# Note: the project must also be a git repository
jest --watch

# Runs tests on all files and re-runs for any new change
jest --watchAll
Enter fullscreen mode Exit fullscreen mode

如何使用 Jest 获取测试覆盖率报告?

要使用 Jest 获取测试覆盖率报告,我们可以使用--coverage 标志

测试覆盖率是一项软件测试指标,用于描述被测单元中有多少行源代码(语句)被测试执行(覆盖)。如果某个单元的测试覆盖率为 100%,则表示该单元中的每一行代码都已被测试调用。

我们应该始终追求高测试覆盖率 - 理想情况下是 100% - 但也请记住,全面覆盖并不意味着我们测试了所有情况,而只测试了代码行。

# Runs tests and prints a test coverage afterwards
jest --coverage
Enter fullscreen mode Exit fullscreen mode

💡注意:
我们可以组合不同的标志来从 Jest 中获得更多功能。例如,要监视所有文件并获取覆盖率报告,我们可以运行jest --watchAll --coverage

这样就大功告成了!现在,我们可以编写测试,并在文件更改时运行它们,还可以查看已覆盖和未覆盖代码行的测试覆盖率报告。


Jest 单元测试示例代码

要安装 Jest:

# For NPM users
npm install --save-dev jest @types/jest

# Yarn users
yarn add --dev jest @types/jest
Enter fullscreen mode Exit fullscreen mode

被测试的单元isInteger.js

// isInteger.js
module.exports = (value) => !isNaN(parseInt(value, 10));
Enter fullscreen mode Exit fullscreen mode

单元测试isInteger.spec.js

// isInteger.spec.js
const isInteger = require("./isInteger");

describe("isInteger", () => {
    const integerNumbers = [-10, -1, 0, 1, 10];

    test.each(integerNumbers)(
        "passes for integer value %j",
        (fixture) => expect(isInteger(fixture)).toBe(true)
    );

    const floatNumbers = [-10.1, -1.1, 0.1, 1.1, 10.1];

    test.each(floatNumbers)(
        "fails for non-integer value %j",
        (fixture) => expect(isInteger(fixture)).toBe(false)
    );
});
Enter fullscreen mode Exit fullscreen mode

测试脚本位于package.json

jest --watchAll --coverage
Enter fullscreen mode Exit fullscreen mode

家庭作业和后续步骤

  • 编写更全面的测试。如何处理字符串?如何处理对象?null以及undefined?考虑添加更多测试用例来覆盖这些情况。
  • 修复代码以便测试通过或编写更新、更好的实现。
  • 在覆盖率报告中实现100%的代码覆盖率。

感谢您花时间阅读这篇文章!

你之前尝试过用 Jest 写单元测试吗?你对 Jest 感觉如何?

留下评论并开始讨论!

文章来源:https://dev.to/dstrekelj/how-to-write-unit-tests-in-javascript-with-jest-2e83
PREV
从加油站炸鸡到成为一名软件工程师!
NEXT
Windsurf 与 Cursor 的初步想法