如何使用Jest测试你的React组件

在本文中,我们将了解如何使用Jest(Facebook 维护的一个测试框架)来测试我们的React组件。我们将首先了解如何在纯 JavaScript 函数上使用 Jest,然后再了解它提供的一些开箱即用的功能,这些功能专门用于使测试 React 应用程序变得更容易。

值得注意的是,Jest 并不是专门针对 React:您可以使用它来测试任何 JavaScript 应用程序。然而,它提供的一些功能对于测试用户界面来说非常方便,这就是为什么它非常适合 React。

应用测试示例

在我们测试任何东西之前,我们需要一个应用程序来测试!您可以在 GitHub 上找到它以及我们即将编写的所有测试。如果您想使用该应用程序来感受一下,您还可以在线找到现场演示

该应用程序使用 ES2015 编写的,使用带有 Babel ES2015 和 React 预设的 webpack 进行编译。我不会详细介绍构建设置,但如果您想查看的话,一切都在 GitHub 存储库中。您可以在自述文件中找到有关如何在本地运行应用程序的完整说明。如果您想了解更多信息,该应用程序是使用webpack构建的,我推荐" Webpack 初学者指南"进行学习。

应用程序的入口是app/index.js

  1. render(

  2. <Todos />,

  3. document.getElementById('app')

  4. );

Todos组件是应用程序的主要内容。它包含所有状态(该应用程序的硬编码数据,实际上可能来自 API 或类似数据),并有渲染两个子组件。

因为Todos组件包含所有状态,所以每当有任何变化时,它需要TodoAddTodo组件通知它。因此,它将函数向下传递到这些组件中,当某些数据发生变化时它们可以调用这些函数,并Todos可以相应地更新状态。

最后,现在您会注意到所有业务逻辑都包含在app/state-functions.js

  1. export function toggleDone(todos, id) {...}

  2. export function addTodo(todos, todo) {...}

  3. export function deleteTodo(todos, id) {...}

如果您熟悉 Redux,它们与 Redux 所谓的reducer非常相似。事实上,如果这个应用程序变得更大,会考虑迁移到 Redux 以获得更明确、结构化的数据方法。

选择 TDD 而不是 TDD?

关于测试驱动开发的优缺点已经有很多文章写过了,开发人员需要先编写测试,然后再编写修复测试的代码。这背后的想法是,通过先编写测试,您必须思考您正在编写的API,并且它可能会导致更好的设计。我认为这在很大程度上取决于个人偏好和测试的类型。我发现,对于React组件,我喜欢先编写组件,然后为最重要的功能添加测试。然而,如果您发现先为组件编写测试符合您的工作流程,那么您应该这样做。在这里没有硬性规定,做适合您和您的团队感觉最好的事情。

什么是Jest

Jest首次发布于2014年,尽管它最初引起了很多关注,但该项目一度停滞不前,并没有得到很积极的开发。然而,Facebook投入了大量精力来改进Jest,并发布了一些令人印象深刻的变化,值得重新考虑。与最初的开源发布相比,Jest仅保留了名称和标志。其他一切都已经改变和重写。如果您想了解更多信息,可以阅读Christoph Pojer的评论,他在其中讨论了该项目的当前状态。

如果您对使用其他框架设置Babel、React和JSX测试感到沮丧,那么我强烈建议您尝试Jest。如果您发现现有的测试安装速度很慢,我也强烈推荐Jest。它可以自动并行运行测试,其watch模式能够运行与更改文件相关的测试,这在您拥有大量测试套件时是非常宝贵的。它已经配置了JSDom,这意味着您可以编写浏览器测试但通过Node运行它们。它可以处理异步测试,并且具有内置的高级功能,如mocking、spy和stub。

安装和配置 Jest

首先,我们需要安装 Jest。因为我们也在使用 Babel,所以我们将安装另外几个模块,使 Jest 和 Babel 可以正常使用:

npm install --save-dev jest babel-jest @babel/core @babel/preset-env @babel/preset-react

您还需要一个babel.config.js包含 Babel 配置的文件,以使用您需要的任何预设和插件。如下所示:

  1. module.exports = {

  2. presets: [

  3. '@babel/preset-env',

  4. '@babel/preset-react',

  5. ],

  6. };

本文不会深入探讨 Babel 的设置。如果您想具体了解有关 Babel 的更多信息,看我以前的文章。

我们还不会安装任何 React 测试工具,因为我们不会从测试组件开始,而是从测试状态函数开始。

Jest 期望在一个文件夹中找到我们的测试__tests__,这已成为 JavaScript 社区中的一种流行惯例,我们将在这里坚持这一惯例。如果您不喜欢该__tests__设置,开箱即用的 Jest 还支持查找任何.test.js文件.spec.js

由于我们将测试我们的状态函数,因此请继续创建__tests__/state-functions.test.js。 下面是一个测试用例,进行测试检查Jest配置是否正常。

  1. describe('Addition', () => {

  2. it('knows that 2 and 2 make 4', () => {

  3. expect(2 + 2).toBe(4);

  4. });

  5. });

现在,进入您的package.json, 添加npm test命令:

  1. "scripts": {

  2. "test": "jest"

  3. }

如果您现在执行npm test命令,您应该会看到测试运行并通过!

  1. PASS __tests__/state-functions.test.js

  2. Addition

  3. ✓ knows that 2 and 2 make 4 (5ms)

  4. Test Suites: 1 passed, 1 total

  5. Tests: 1 passed, 1 total

  6. Snapshots: 0 passed, 0 total

  7. Time: 3.11s

如果您曾经使用过 Jasmine 或其他测试框架,Jest允许我们使用describe和it来嵌套测试。您使用多少嵌套取决于您。我喜欢嵌套我的测试,这样所有传递给describe和it的描述字符串几乎就像一个句子一样。

当涉及到实际断言时,您需要将要测试的内容包装在expect()调用中,然后在其上调用断言。在这种情况下,我们使用了toBe。您可以在Jest文档中找到所有可用断言的列表。toBe使用===来检查给定值是否与测试的值匹配。通过本教程,我们将遇到一些Jest的断言。

测试业务逻辑

我们将测试我们的第一个状态函数toggleDonetoggleDone接收当前状态和我们想要切换的todo的ID。每个todo都有一个完成属性,而toggleDone应该将其从true切换为false,反之亦然。 注意:如果您正在跟随本文,确保已经克隆了代码库,并将app文件夹复制到包含您的__tests__文件夹的同一目录中。您还需要安装所有应用程序的依赖项(例如React)。在克隆存储库后运行npm install即可确保全部安装完成。

我将从app/state-functions.js导入函数并设置测试的结构开始。当Jest允许您使用describeit进行嵌套时,您也可以使用test,这通常更易于阅读。test只是Jest it函数的别名,但有时可以使测试更易于阅读,减少嵌套。例如,以下是我如何使用嵌套的describeit调用编写该测试的方法:

  1. import { toggleDone } from '../app/state-functions';

  2. describe('toggleDone', () => {

  3. describe('when given an incomplete todo', () => {

  4. it('marks the todo as completed', () => {

  5. });

  6. });

  7. });

这是test用法:

  1. import { toggleDone } from '../app/state-functions';

  2. test('toggleDone completes an incomplete todo', () => {

  3. });

测试仍然很容易读懂,但现在缩进少了一些。这主要取决于个人喜好;选择您更喜欢的风格即可。

现在我们可以编写断言了。首先,我们将创建我们的起始状态,然后将其传递到toggleDone中,以及我们要切换的todo的ID。toggleDone将返回我们的最终状态,然后我们可以对其进行断言:

  1. import { toggleDone } from "../app/state-functions";

  2. test("tooggleDone completes an incomplete todo", () => {

  3. const startState = [{ id: 1, done: false, text: "Buy Milk" }];

  4. const finState = toggleDone(startState, 1);

  5. expect(finState).toEqual([{ id: 1, done: true, text: "Buy Milk" }]);

  6. });

现在我使用toEqual进行断言。您应该使用toBe来测试原始值(例如字符串和数字),使用toEqual测试对象和数组。toEqual是专门用于处理数组和对象的,并且会递归检查给定对象内的每个字段或项目以确保其匹配。

有了这个,我们现在可以运行npm test并看到我们的状态函数测试通过:

  1. PASS __tests__/state-functions.test.js

  2. ✓ tooggleDone completes an incomplete todo (9ms)

  3. Test Suites: 1 passed, 1 total

  4. Tests: 1 passed, 1 total

  5. Snapshots: 0 passed, 0 total

  6. Time: 3.166s

配置热更新

在更改测试文件后,手动再次运行npm test有点繁琐。 Jest最好的功能之一是其监视模式,它会监视文件更改并相应地运行测试。它甚至可以根据更改的文件确定要运行的子集测试。它非常强大和可靠,您可以在监视模式下运行Jest,并在整天编写代码时保留它。

要在监视模式下运行它,您可以运行npm test -- watch。在第一个--之后传递给npm test的任何内容都将直接传递给基础命令。这意味着这两个命令是等效的:

  • npm test -- --watch
  • jest --watch

我建议您在另一个终端窗口中将Jest保留运行,以便在本教程的其余部分中使用。

在继续测试React组件之前,我们将在另一个状态函数上编写一个测试。在实际应用程序中,我会编写更多的测试,但是为了本教程的缘故,我将跳过其中一些。现在,让我们编写一个测试,以确保我们的deleteTodo函数正常工作。在查看下面我的编写方式之前,请尝试自己编写它并查看您的测试与我的测试有何不同。

请记住,您需要更新顶部的导入语句以导入deleteTodotoggleTodo

import { toggleDone, deleteTodo } from "../app/state-functions";

这是我编写测试用例:

  1. test('deleteTodo deletes the todo it is given', () => {

  2. const startState = [{ id: 1, done: false, text: 'Buy Milk' }];

  3. const finState = deleteTodo(startState, 1);

  4. expect(finState).toEqual([]);

  5. });

这个测试与第一个测试并没有太大的区别:我们设置起始状态,运行我们的函数,然后对最终状态进行断言。如果您已在监视模式下运行Jest,请注意它如何检测到您的新测试并运行它,以及它运行速度的快速性!这是一种在写测试时立即得到反馈的好方法。

上面的测试还演示了测试的过程,其中包括:

  • 设置
  • 执行要测试的功能
  • 对结果进行断言

通过以这种方式布局测试,您会发现更容易跟踪和处理它们。

现在我们已经满意测试我们的状态函数,让我们继续测试React组件。

测试 React 组件

值得注意的是,默认情况下,我实际上鼓励您不要在React组件上编写太多测试。您想要非常彻底测试的任何内容,例如业务逻辑,都应该从组件中提取出来并作为独立的函数存在,就像我们之前测试的状态函数一样。尽管如此,有时测试一些React交互(例如,确保用户单击按钮时调用特定函数并传递正确参数)是很有用的。我们将从测试我们的React组件是否呈现正确的数据开始,然后再看看如何测试交互。

为了编写测试,我们将安装Enzyme,这是由Airbnb编写的包装库,使测试React组件变得更加容易。

注意:自本文首次编写以来,React团队已经转向Enzyme,并推荐使用[React Testing Library(RTL)](https://testing-library.com/docs/react-testing-library/intro)。阅读该页面很值得一看。如果您正在维护已经使用Enzyme测试的代码库,则无需放弃所有内容并转移,但对于新项目,我建议考虑RTL

除了Enzyme之外,我们还需要安装适用于我们使用的React版本的适配器。对于React v16,使用enzyme-adapter-react-16,但对于React v17,目前没有官方适配器可用,因此我们必须使用非官方版本。请注意,该软件包旨在作为临时措施,直到正式支持发布,并将在那时被弃用。

npm install --save-dev enzyme @wojtekmaj/enzyme-adapter-react-17

我们需要对 Enzyme 进行少量设置。在项目的根目录中,创建setup-tests.js以下代码并将其放入其中:

  1. import { configure } from 'enzyme';

  2. import Adapter from '@wojtekmaj/enzyme-adapter-react-17';

  3. configure({ adapter: new Adapter() });

然后,我们需要告诉Jest在执行任何测试之前为我们运行此文件。我们可以通过配置setupFilesAfterEnv选项来实现这一点。您可以将Jest配置放在自己的文件中,但我喜欢使用package.json并将东西放在jest对象中,Jest也会自动检测到它:

  1. "jest": {

  2. "setupFilesAfterEnv": [

  3. "./setup-tests.js"

  4. ]

  5. }

现在我们准备编写一些测试了!让我们测试该Todo组件是否在段落内呈现其待办事项的文本。首先我们将创建__tests__/todo.test.js并导入我们的组件:

  1. import Todo from '../app/todo';

  2. import React from 'react';

  3. import { mount } from 'enzyme';

  4. test('Todo component renders the text of the todo', () => {

  5. });

Enzyme导入了mountmount函数用于呈现我们的组件,然后允许我们检查输出并对其进行断言。即使我们在Node中运行测试,我们仍然可以编写需要DOM的测试。这是因为Jest配置了jsdom,一个在Node中实现DOM的库。这很好,因为我们可以编写基于DOM的测试,而无需每次启动浏览器进行测试。

可以使用mount来创建我们的Todo

  1. const todo = { id: 1, done: false, name: 'Buy Milk' };

  2. const wrapper = mount(

  3. <Todo todo={todo} />

  4. );

然后,我们可以调用wrapper.find,给它一个CSS选择器,以查找我们希望包含Todo文本的段落。这个API可能会让您想起jQuery,这是有意设计的。这是一个在渲染的输出中搜索匹配元素的非常直观的API。

const p = wrapper.find('.toggle-todo');

最后,我们可以断言其中的文本是Buy Milk

expect(p.text()).toBe('Buy Milk');

完整的测试用例是这样的:

  1. import Todo from '../app/todo';

  2. import React from 'react';

  3. import { mount } from 'enzyme';

  4. test('TodoComponent renders the text inside it', () => {

  5. const todo = { id: 1, done: false, name: 'Buy Milk' };

  6. const wrapper = mount(

  7. <Todo todo={todo} />

  8. );

  9. const p = wrapper.find('.toggle-todo');

  10. expect(p.text()).toBe('Buy Milk');

  11. });

现在我们有了一个测试,可以检查我们是否成功地渲染了todos。 接下来,让我们看一下如何使用Jest的spy功能来断言具有特定参数的函数被调用。在我们的情况下很有用,因为我们有一个Todo组件,它被赋予了两个函数作为属性,当用户单击按钮或执行交互时应该调用这些函数。

在这个测试中,我们将断言当Todo被单击时,组件将调用它所赋予的doneChange属性:

  1. test('Todo calls doneChange when todo is clicked', () => {

  2. });

我们希望有一个可以用来跟踪其调用及其调用参数的函数。然后,我们可以检查当用户单击Todo时,doneChange函数是否被调用,并且被调用时使用了正确的参数。幸运的是,Jest在功能上提供了这个功能来使用spy(间谍)spy是一种函数,其实现方式并不重要,您只需要关心它何时以及如何被调用。可以将其想象为您在侦察该函数。要创建一个spy,我们调用jest.fn()

const doneChange = jest.fn(); 

这提供了一个函数,我们可以对其进行监视并确保其被正确调用。让我们从使用正确的props来呈现我们的Todo开始:

  1. const todo = { id: 1, done: false, name: 'Buy Milk' };

  2. const doneChange = jest.fn();

  3. const wrapper = mount(

  4. <Todo todo={todo} doneChange={doneChange} />

  5. );

接下来,我们可以再次查找我们的段落,就像在前面的测试中一样:

const p = wrapper.find(".toggle-todo"); 

然后我们可以调用simulate它来模拟用户事件,并click作为参数传递:

p.simulate('click');

现在我们只需要断言我们的spy函数是否被正确调用。在这种情况下,我们期望它被调用时使用了ID为1的todo。我们可以使用expect(doneChange).toBeCalledWith(1)来断言这一点,然后我们的测试就完成了!

  1. test('TodoComponent calls doneChange when todo is clicked', () => {

  2. const todo = { id: 1, done: false, name: 'Buy Milk' };

  3. const doneChange = jest.fn();

  4. const wrapper = mount(

  5. <Todo todo={todo} doneChange={doneChange} />

  6. );

  7. const p = wrapper.find('.toggle-todo');

  8. p.simulate('click');

  9. expect(doneChange).toBeCalledWith(1);

  10. });

结论

Facebook很久以前就发布了Jest,但最近已经被广泛采用和改进。它已经快速成为JavaScript开发人员的首选,而且它只会变得更好。如果你以前尝试过Jest并且不喜欢它,我无法足够地鼓励你再次尝试它,因为现在它基本上是一个完全不同的框架。它快速,重复运行规格非常出色,提供了出色的错误消息,并且有一个出色的表达式API,用于编写良好的测试。

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

视频文档获取方式:

这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行

相关推荐
xiao-xiang6 分钟前
jenkins-通过api获取所有job及最新build信息
前端·servlet·jenkins
C语言魔术师22 分钟前
【小游戏篇】三子棋游戏
前端·算法·游戏
匹马夕阳2 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?2 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
screct_demo3 小时前
詳細講一下在RN(ReactNative)中,6個比較常用的組件以及詳細的用法
javascript·react native·react.js
桂月二二8 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062069 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb9 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角9 小时前
CSS 颜色
前端·css
九酒9 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae