使用 React.js 和 Jest 像老板一样测试你的应用程序
在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris
在本文中,我们将介绍测试框架 Jest。我们将学习如何:
- 编写测试,编写测试并根据特定条件进行断言非常简单
- 通过利用模式匹配功能运行特定测试以及特定测试文件来管理我们的测试套件
- 调试我们的测试,通过增强 VS Code,我们可以在测试中设置断点,并创造非常好的调试体验
- 快照精通,了解如何使用快照可以增强您的信心,确保您的组件在进行更改后仍然正常工作
- 利用模拟,模拟依赖项可以确保你只测试你想要测试的内容,并且 Jest 在模拟方面有很好的默认设置
- 覆盖率报告,我们期望所有优秀的测试库都包含一个优秀的覆盖率工具。Jest 也不例外,它可以轻松运行覆盖率报告,并快速找到代码中哪些部分可以通过进一步测试受益。
Jest 的卖点是
令人愉悦的 JavaScript 测试
它令人欣喜的地方在于它宣称拥有零配置体验。
好的,我们越来越接近答案了。
- 由于工作人员的努力,并行运行的测试表现出色。
- 内置覆盖工具
- 借助ts-jest,可与 TypeScript 配合使用
开始
让我们尝试设置一下,看看需要多少配置。如果你只是想试试,可以使用Jest REPL来编写测试等等。
编写我们的第一个测试
为了让测试运行器找到测试,我们需要遵循以下三个约定之一:
- 创建一个
__tests__
目录并将文件放入其中 - 使文件匹配
*spec.js
- 使文件匹配
.test.js
好的,现在我们知道 Jest 如何找到我们的文件,那么编写一个测试怎么样?
// add.js
function add(a, b) {
return a + b;
}
module.exports = add;
// add.spec.js
const add = require('../add');
describe('add', () => {
it('should add two numbers', () => {
expect(add(1, 2)).toBe(3);
});
});
上面我们看到,我们使用它describe
来创建测试套件,并在测试套件中创建测试。我们还看到我们使用它对expect
结果进行断言。它expect
让我们可以访问很多东西matchers
,匹配器是我们在期望之后调用的一个函数:
期望(某物).匹配器(值)
正如您在我们的测试示例中看到的,我们使用了一个名为的匹配器,toBe()
它基本上将期望中的内容与匹配器中的内容进行匹配,例如:
expect(1).toBe(1) // succeeds
expect(2).toBe(1) // fails
匹配器有很多,所以我建议你看看现有的匹配器,并尝试使用合适的匹配器
运行测试
我们能做的最简单的事情就是使用 创建一个项目create-react-app
,因为 Jest 已经在那里设置好了。一旦我们创建了项目并安装了所有依赖项,我们就可以简单地运行:
纱线测试
它将显示上面的图像,其中包含:
- 一个执行的测试套件,
- 一个通过测试并运行了一系列命令的程序,我们稍后会进行探讨。它似乎已经运行了文件
src/App.test.js
。
让我们看一下该文件:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
我们可以看到,它使用它创建了一个测试,并且还使用创建了一个组件ReactDOM.render(<App />, div)
,然后通过调用进行了清理ReactDOM.unmount(div)
。目前我们还没有做任何断言,我们只是尝试创建一个没有错误的组件,不过知道这一点还是不错的。
add.js
我们尝试添加文件及其相应的测试怎么样?
我们首先添加add.js
,像这样:
// add.js
function add(a,b) { return a + b; }
export default add;
接下来是测试:
// add.spec.js
import add from '../add';
it('testing add', () => {
const actual = add(1,3);
expect(actual).toBe(4);
});
我们的 Jest 会话仍在终端中运行:
我们可以看到现在有两个通过的测试。
调试
任何优秀的测试运行器/框架都应该能够让我们调试测试。它应该让我们能够:
- 运行特定测试
- 忽略测试
- 设置断点,让我们在 IDE 中添加断点(更多取决于 IDE 供应商来实现)
- 在浏览器中运行,让我们在浏览器中运行测试
运行特定的测试文件
让我们看看如何做这些事情,先从运行具体的测试开始。首先,我们将添加另一个文件subtract.js
和一个相应的测试。
// subtract.js
function subtract(a,b) {
return a - b;
}
export default subtract;
和测试:
// subtract.spec.js
import subtract from '../subtract';
it('testing subtract', () => {
const actual = subtract(3,2);
expect(actual).toBe(1);
});
让我们再次看一下我们的终端,特别是它的底部:
如果您没有看到此信息,w
请按照屏幕上的提示进行操作。以上提供了一系列命令,可简化我们的调试:
a
,运行所有测试p
,这将允许我们指定一个模式,通常我们希望在这里指定一个文件的名称,以使其只运行该文件。t
,它的作用与 p 相同,但它允许我们指定测试名称q
,退出观看模式Return
,触发测试运行
根据上述描述,我们将尝试将其过滤以仅测试add.js
文件,因此我们输入p
:
这会将我们带到一个模式对话框,我们可以在其中输入文件名。我们这样做:
从上面我们可以看到,只有add.js
文件才会成为目标。
运行特定测试
我们已经学习了如何将范围缩小到特定文件。我们甚至可以使用p
, 模式方法将其缩小到特定的测试。首先,我们需要添加一个测试,以便进行实际筛选:
//add.spec.js
import add from '../add';
it('testing add', () => {
const actual = add(1,3);
expect(actual).toBe(4);
});
it('testing add - should be negative', () => {
const actual = add(-2,1);
expect(actual).toBe(-1);
});
此时我们的终端如下所示:
假设我们在同一个文件中有两个已通过的测试,但我们只想运行其中特定的一个。为此,我们通过.only()
在测试中添加调用来实现,如下所示:
it.only('testing add', () => {
const actual = add(1,3);
expect(actual).toBe(4);
});
现在终端看起来如下:
.only()
我们可以看到,如果我们只想运行该测试,添加效果很好。我们可以使用.skip()
来让测试运行器跳过特定的测试:
it.skip('testing add', () => {
const actual = add(1,3);
expect(actual).toBe(4);
});
终端结果清楚地表明我们跳过了一项测试:
使用断点进行调试
现在,这个功能有点依赖于 IDE,本节我们将介绍如何在 VS Code 中实现。我们要做的第一件事是安装一个扩展。前往扩展菜单,搜索“Jest”。应该会显示以下内容:
安装此扩展并返回到您的代码。现在我们添加了一些功能。我们所有的测试都应该包含一个Debug link
。
此时,我们可以添加一个断点,然后按下Debug link
。你的断点现在应该被触发,如下所示:
快照测试
快照功能用于创建快照,即渲染组件时 DOM 的视图。它用于确保当您或其他人对组件进行更改时,快照能够告知您所做的更改是否正常。
如果您同意所做的更改,则可以轻松地使用现在渲染的 DOM 更新快照。因此,快照是您的朋友,可以保护您免受意外更改的影响。
让我们看看如何创建快照。首先,我们可能需要安装一个依赖项:
yarn add react-test-renderer --save
下一步是编写一个组件和一个与之配合的测试。它应该看起来像这样:
// Todos.js
import React from 'react';
const Todos = ({ todos }) => (
<React.Fragment>
{todos.map(todo => <div>{todo}</div>)}
</React.Fragment> );
export default Todos;
// Todos.spec.js
import renderer from 'react-test-renderer';
import React from 'react';
import Todos from '../Todos';
test('Todo - should create snapshot', () => {
const component = renderer.create(
<Todos todos={['item1', 'item2']} />
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
})
注意如何导入imports
我们将要测试的组件:
从'../Todos'导入Todos;
接下来使用渲染器创建组件的实例。下一步是将组件转换为 JSON 格式,如下所示。component.toJSON()
最后,我们通过调用 进行断言expect(tree).toMatchSnapshot()
,这将调用一个快照,并将其自身放置在__snapshots__
测试目录下的目录中。
管理快照
好的,我们有了快照,现在做什么?让我们对 todo 组件进行一些更改,如下所示:
// Todos.js
import React from 'react';
const Todos = ({ todos }) => (
<React.Fragment> {
todos.map(todo => (
<React.Fragment>
<h3>{todo.title}</h3> <div>{todo.description}</div>
</React.Fragment>
))}
</React.Fragment> );
export default Todos;
我们看到 ourtodo
是一个对象而不是字符串,所以它有一个title
anddescription
属性。这将使我们的快照做出反应,并显示以下内容:
它清楚地表明存在一些差异,并要求我们检查代码。如果我们对更改满意,就应该点击u
将快照更新到新版本。查看代码,是的,这是预期的更改,因此我们点击u
。最终我们得到以下图像,告诉我们一切正常:
嘲讽
Mocking 是那些需要良好运行的事情之一。在 Jest 中使用 Mocking 非常简单。你需要在与模块相邻的目录中创建你的 mock,或者更确切地说,是在模块的子目录中创建。让我们用代码来演示一下。假设你有以下模块:
// repository.js
const data = [{ title: 'data from database' }];
export default data;
让我们看一下这个测试:
// repository.spec.js
import data from '../repository';
describe('testing repository data', () => {
it('should return 1 item', () => {
console.log(data);
expect(data.length).toBe(1);
});
});
虽然不是最好的测试,但总归是一个测试。让我们创建模拟,使我们的文件结构如下所示:
// directory structure
repository.js // our repo file
__mocks__/repository.js // our mock
我们的模拟应该是这样的:
// __mock__/repository.js
const data = [{ title: 'mocked data' }];
export default data;
要使用这个模拟,我们需要jest.mock()
在测试内部调用,如下所示:
// repository.spec.js
import data from '../repository';
jest.mock('../repository'); // taking __mock/repository instead of the actual one
describe('testing repository data', () => {
it('should return 1 item', () => {
console.log(data);
expect(data.length).toBe(1);
});
});
现在它使用我们的模拟模块,而不是实际的模块。好吧,你可能会问,我为什么要模拟我想要测试的东西呢?简而言之:你不会。因此,我们将创建另一个consumer.js
使用我们的文件repository.js
。让我们看看它的代码及其对应的测试:
// consumer.js
import data from './repository';
const item = { title: 'consumer' };
export default [ ...data, { ...item}];
上面我们清楚地看到了消费者如何使用我们的模块repository.js
,现在我们想模拟它,以便我们能够专注于测试消费者模块。让我们看一下测试:
// consumer.spec.js
import data from '../consumer';
jest.mock('../repository');
describe('testing consumer data', () => {
it('should return 2 items', () => {
console.log(data);
expect(data.length).toBe(2);
});
});
我们使用jest.mock()
并模拟该模块唯一的外部依赖。
那么像lodash
或 这样的库呢jquery
?它们不是我们创建的模块,但却依赖它们。我们可以通过创建__mocks__
目录来为最高级别的库创建模拟。
关于模拟还有很多可以说的,更多详细信息请查看文档模拟文档
覆盖范围
我们已经到了本章的最后一节。本节主要介绍如何了解测试覆盖了多少代码。为了检查这一点,我们只需运行:
纱线测试覆盖率
这会在终端中生成一个表格,以百分比形式显示每个文件的覆盖率。它还会生成一个coverage
目录,我们可以在其中找到覆盖率的 HTML 报告。首先,让我们修改一下文件add.js
,添加一段需要测试的逻辑,如下所示:
// add.js
function add(a, b) {
if(a > 0 && b > 0 ) {
return a + b;
}
throw new Error('parameters must be larger than zero');
}
export default add;
现在我们可以看到,应用程序中有不止一条路径。如果输入参数大于零,那么我们就有现有的测试覆盖它。
但是,如果一个或多个参数小于零,我们就会进入一个新的执行路径,而该路径未被测试覆盖。让我们通过导航到 来查看覆盖率报告中的内容coverage/lcov-report
。例如,我们可以通过输入
http服务器-p 5000
我们将得到如下报告:
现在我们可以导航到src/add.js
它应该看起来像这样:
现在我们可以清楚地看到我们添加的代码是如何用红色表示的,并且我们需要添加一个测试来覆盖新的执行路径。
接下来,我们添加一个测试来解决这个问题,如下所示:
// add.spec.js
import add from '../add';
describe('add', () => {
it('testing addition', () => {
const actual = add(1,2);
expect(actual).toBe(3);
});
it('testing addition with neg number', () => {
expect(() => { add(-1,2); }).toThrow('parameters must be larger than zero'); })
})
)
现在,我们的第二种情况应该涵盖了导致抛出异常的执行路径。让我们重新运行覆盖率报告:
概括
我们了解了如何编写测试。我们还学习了如何使用 VS Code 的扩展程序来调试测试,该扩展程序允许我们设置断点。
此外,我们还了解了什么是快照以及如何最好地利用它们。
接下来,我们一直在研究利用模拟来确保我们在测试时处于完全隔离状态。
最后,我们研究了如何生成覆盖率报告以及如何帮助您追踪真正需要更多测试的代码部分。