作者 | Sanjeev Sharma
译者 | 许学文
策划 | 丁晓昀
你是否正计划为你的 React 代码编写测试?是否因找不到好的入门教程而感到苦恼?那么,这篇文章正是你所需要的。在本文中,我们将涵盖编写单元测试的所有步骤以及这个过程中可能遇到的错误和问题。
本文使用了Jest和React Testing Library库。如果你想使用其他库也没关系,文中的一些基础知识也会对你有所帮助。
本文涉及的全部代码都托管在 GitHub 上,文末提供了链接地址。
为什么要编写测试
当然,不写测试代码也可以完成产品开发。用户、产品经理,甚至测试人员或 QA 都不在乎该产品是否有测试代码。但是你,作为一名开发人员,应该在乎!
假设你有一个拥有数万用户的网站,当你对一个公共的工具函数做了一些重构(或添加了一个功能修复),并在应用中某个调用它的地方进行了测试,表明该函数可以正常运行。于是你选择在周五上线(这是个低级错误)。然后,该函数在应用中的其他地方无法运行,导致网站在周末期间出现线上故障。 此时,你多么希望这些地方能有测试代码,可以在发布生产之前自动运行,从而避免此次故障。
上述场景比你想象的还要普遍。你可能还没遇到过(不过,这是迟早的事情),但是包括我在内的很多工程师都已经遇到过了。
因此,测试代码之所以很重要,主要有以下几个原因:
- 增强你对代码发布上线的信心。
- 测试代码本身也是一种文档。
️- 有助于调试和重构。 - 从长远来看,有助于减少开发时间。
对于所有希望晋升的初级开发人员来说,务必要具备编写测试代码的能力。
测试教程
我们将从零开始教你编写测试代码,所以请准备好终端。首先,我们使用 vite 创建一个示例项目。
在创建项目后,使用以下命令运行它。
程序运行之后,你会在页面上看到一个 demo 应用。
我们不会给该应用添加新功能,但为了给按钮编写测试代码,我们需要将按钮重构为一个单独的组件。
接下来,我们在页面上添加两个按钮:
- 一个按钮的功能是点击时将 count 的值乘以 2。
- 另外一个按钮的功能是点击时按以下顺序执行操作:如果 count 的值以 0 结尾,那么就将它的值除以 2。如果 count 的值是斐波那契数,那么就将它的值加 1。否则,将 count 的值进行平方操作。
我们需要在 utils 模块中声明上面代码中用到的两个函数。同时,我们也声明了一些辅助函数,但由于其他地方并不会用到这些辅助函数,因此这里不需要做导出。
代码已经准备好,现在可以开始编写测试代码了。这里我们跳过 React 代码,直接先给工具函数编写测试。这有助于我们了解 Jest 框架的大致用法。
下面,让我们为 doubleTheNum 函数编写测试。
上面的代码用于测试我们的函数是否可以按预期执行。任何测试代码都会包含以下这些关键组件:
- describe 函数:第一个参数是字符串,它会在测试运行的时候显示。第二个参数则是测试实际执行的函数。describe 函数的主要作用是对同类型的测试进行分组。这里只有一个测试,在另外一个示例中,你会看到其中有多个测试。
- **it **函数:其参数结构和 describe 函数类似。但这里的字符串参数应该尽可能详细地描述测试函数的具体内容。当然,你也可以使用 test 函数替代 it。
- expect 语句块:此函数中的前三行很简单。其最后一行是通过断言来判断 doubleTheNum 函数能否正确运行。此外,这里我们还用到了 toEqual 匹配器函数。
Jest 提供了很多匹配器,例如:
- toBeNull 用于匹配 null。
- toBeTruthy 用于匹配判定结果为 true 的语句。
想了解关于匹配器的更多信息,参考如下链接:
https://jestjs.io/docs/using-matchers
为了运行测试,我们需要先安装 Jest:
然后在 package.json 中添加测试脚本:
最后,通过执行 yarn test 命令来运行测试。
对于大多数人来说,上面的步骤已经足够了。但如果你遇到了与模块导入或 TypeScript 相关的任何问题,请按以下步骤进行操作:
- 安装并设置 @babel/preset-env:
然后,将它配置到 package.josn 中:
- 安装 TypeScript 依赖库:
然后,在 jest.config.ts 中添加 Jest 配置:
然后执行测试,结果如下:
从输出中可以看到我们在 describe 和 it 函数中声明的字符串信息。
恭喜,你完成了第一个测试!
喜欢这篇文章吗?如果觉得还不错,我推荐你看看我的另一篇最受欢迎的文章《Redux完整指南》,阅读量高达 2.5 万:<br/>https://dev.to/thesanjeevsharma/just-redux-the-complete-guide-44d5
接下来,我们给 funkyNum 函数编写测试。
编写测试时,应该尽量多地覆盖函数的分支和语句。测试覆盖率越高会让人越有信心。
如果你再次运行测试,应该会看到以下输出。
理想情况下,我们也应该为 isFibonacci 和 isPerfectSquare 函数编写单独的 describe 语句块。在单元测试中,测试代码应该是互相独立的。简洁起见,这里我们没有这样做。
小提示
- 通过调用.skip 或 test.skip 来跳过任何测试,或调用 describe.skip 来跳过整个测试块。
- 通过调用 it.only 或 test.only 执行单个测试。
上面我们已经介绍了如何使用 Jest 进行 JS 代码的测试。现在,让我们深入探讨下关于 React 的测试。
在开始之前,我们还需要安装一些依赖库:
同时,还需要在 jest.config.ts 中添加环境:
下面我们给 CounterButton 组件编写一个最基础的测试:
在上面的代码中,我们提供了所需的 props ,并尝试渲染组件。对于任何组件,这都应该是你为它编写的第一个测试。因为如果该测试无法通过,那么其他测试就毫无用处。
RTL(React Testing Library)的 render 函数将在 document.body 中渲染传入的组件。
它还返回了一些诸如 getByText 这样的查询方法,可用于在 DOM 中查找元素。
点击这里查阅所有的查询方法。
如果你再次运行测试,应该可以看到 2 组测试——全部为绿色且通过。
我们编写的第二个测试是测试组件对于 props 的反应。如果各个 prop 之间没有互相依赖,那么应该为每个 prop 参数都编写单独的测试。
getByText 函数是一种查询方法,可以让我们通过字符串来获取元素。
toBeInTheDocument 函数是一个和 toEqual 类似的匹配器。Jest 默认不提供该函数,需要在安装 @testing-library/jest-dom 库之后才能使用。
不同的环境有不同的包,例如在 React Native 环境中,需要使用 @testing-library/jest-native。
如果你再次运行测试,测试应该也会通过。
最后,我们来编写本文的最后一个测试,同时也是最重要的一个。我们将编写一个测试来检查点击事件处理程序是否按预期工作。
为了生成用户事件(例如点击和按键),我们需要安装另外一个包。
与之前的测试代码相比,这次的测试代码几乎一样,只有一些微小的差异。
注意:由于是模拟用户事件,所以该函数异步执行。
第一行的 jest.fn()是一个模拟函数。在测试运行时,可以通过它跟踪诸如调用参数、调用次数等很多非常有用的信息。类似这样的函数,以后你会看到很多。
我们还使用了一种新的查询方法 getByRole 来查找按钮元素。
在检查模拟函数是否被调用之前,我们需要先等待点击事件完成。
就是这样!如果你运行测试,它们应该也会通过。
这里获取所有代码
下一步
如果你遵循本文成功地完成了测试代码的编写,那么你可以开始在自己的代码库中添加测试代码并且进一步探索各种测试功能了。
另外,我建议你进一步了解以下几个方面的内容:
- getByTestId——这是一个使用很普遍的查询方法。当其他方法都不好使的时候,可以用它。
- 了解Setup和Teardown方法。它将提升你的测试水平。
- 学习如何模拟 npm 模块、API 调用、全局状态和上下文等。