作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Anton是一名拥有强大技术背景的全栈开发人员. 他专门研究JavaScript,是测试驱动开发的粉丝.
集成测试是测试成本和价值之间的平衡点. 在React测试库的帮助下编写React应用程序的集成测试,而不是组件单元测试,可以在不影响开发速度的情况下提高代码的可维护性.
如果你想在我们继续之前抢先一步的话, 你可以看到一个如何使用React -testing-library进行React应用集成测试的例子 here.
“集成测试在信心和速度/成本之间取得了很好的平衡. 这就是为什么建议你把大部分精力(注意,不是全部)花在这上面."
– Kent C. Dodds in Write tests. Not too many. 主要集成.
为React组件编写单元测试是一种常见的做法, often using a popular library for testing React “enzymes”; specifically, 其“浅”法. 这种方法允许我们在独立于应用程序其余部分的情况下测试组件. However, 因为编写React应用程序都是关于组合组件的, 单元测试本身并不能确保应用程序没有bug.
For example, 更改组件的可接受的props并更新其相关的单元测试可能会导致所有测试都通过,而如果另一个组件没有相应地更新,应用可能仍然会崩溃.
在对React应用程序进行更改时,集成测试可以帮助保持平和的心态, 因为它们确保组件的组合产生所需的用户体验.
这里有一些东西 开发人员反应 want 编写集成测试时要做的事情:
现在,对于一些事情,我们应该试着 avoid 在编写React应用程序集成测试时:
上述要求使 react-testing-library a great choice, 因为它的主要指导原则是允许React组件以一种类似于人类实际使用它们的方式进行测试.
库,以及它的可选 同伴库,允许我们编写与DOM交互并断言其状态的测试.
我们将为其编写示例的应用程序 集成测试 实现一个简单的场景:
从集成测试的角度来看,上述功能是如何实现的应该是无关的. 然而,为了贴近现实世界的应用程序,应用程序遵循通用 React patterns,因此有了这个应用程序:
可以找到应用程序实现的源代码 here.
With yarn:
Yarn添加——dev jest @testing-library /反应 @testing-library /用户事件 jest-dom nock
Or with npm:
npm i -D jest @testing-library /反应 @testing-library /用户事件 jest-dom nock
我们将创建一个名为 viewGitHubRepositoriesByUsername.spec.js
file in the ./test
应用程序的文件夹. Jest会自动把它捡起来.
import React from 'react'; // so that we can use JSX syntax
import {
render,
cleanup,
waitForElement
} from '@testing-library /反应'; // testing helpers
从“@testing-library /用户事件”//中导入userEvent
import 'jest-dom/extend-expect'; // to extend Jest's expect with DOM assertions
import nock from 'nock'; // to mock github API
import {
FAKE_USERNAME_WITH_REPOS,
FAKE_USERNAME_WITHOUT_REPOS,
FAKE_BAD_USERNAME,
REPOS_LIST
} from './fixtures/github'; // test data to use in a mock API
import './helpers/initTestLocalization'; // to configure i18n for tests
导入应用程序../App'; // the app that we are going to test
describe('view GitHub repositories by username', () => {
beforeAll(() => {
诺(http://api.github.com')
.persist()
.get(' /用户/ $ {FAKE_USERNAME_WITH_REPOS} /回购”)
.query(true)
.REPOS_LIST回复(200);
});
afterEach(清理);
describe('when GitHub user has public repositories', () => {
it('user can view the list of public repositories for entered GitHub username', async () => {
// arrange
// act
// assert
});
});
describe('when GitHub user has no public repositories', () => {
它('用户显示一条消息,没有公共存储库输入GitHub用户名'), async () => {
// arrange
// act
// assert
});
});
describe('when GitHub user does not exist', () => {
it('user is presented with an error message', async () => {
// arrange
// act
// assert
});
});
});
Notes:
describe
块指定集成测试用例和流变化.it
块使用异步回调,因为它们正在测试的用例中有异步步骤.首先,需要渲染应用程序.
const { getByText, getByPlaceholderText, queryByText } = render( );
The render
方法导入 @testing-library /反应
模块在测试React DOM中渲染应用,并返回绑定到渲染应用容器的DOM查询. 这些查询用于定位要与之交互和断言的DOM元素.
Now, 作为测试流程的第一步, 用户将看到一个用户名字段,并在其中键入一个用户名字符串.
userEvent.类型(getByPlaceholderText (userSelection.usernamePlaceholder”),FAKE_USERNAME_WITH_REPOS);
The userEvent
外来佣工 @testing-library /用户事件
module has a type
方法,该方法模仿用户在文本字段中键入文本时的行为. 它接受两个参数:接受输入的DOM元素和用户键入的字符串.
用户通常通过与其相关联的文本来查找DOM元素. 在输入的情况下,它是标签文本或占位符文本. The getByPlaceholderText
返回的查询方法 render
允许我们通过占位符文本查找DOM元素.
请注意,因为文本本身经常有可能改变, 最好不要依赖实际的本地化值,而是将本地化模块配置为返回本地化项键作为其值.
例如,“en-US”本地化通常会返回 输入GitHub用户名
的值 userSelection.usernamePlaceholder
Key,在测试中,我们希望它返回 userSelection.usernamePlaceholder
.
当用户在字段中输入文本时,他们应该看到文本字段值被更新.
期望(getByPlaceholderText (userSelection.usernamePlaceholder ')).FAKE_USERNAME_WITH_REPOS toHaveAttribute(“价值”);
在流程的下一步,用户单击提交按钮,并期望看到存储库列表.
userEvent.点击(getByText (userSelection.submitButtonText”).最近的(“按钮”));
getByText(“存储库.header');
The userEvent.click
方法模拟用户单击DOM元素,而 getByText
查询通过包含的文本查找DOM元素. The closest
Modifier确保我们选择了正确类型的元素.
Note: 在集成测试中,步骤通常服务于两者 act
and assert
roles. 例如,我们断言用户可以通过单击按钮来单击按钮.
在前面的步骤中,我们断言用户看到了应用程序的存储库列表部分. Now, 我们需要断言,因为从GitHub获取存储库列表可能需要一些时间, 用户看到抓取正在进行的指示. 我们还希望确保应用程序不会告诉用户没有与输入的用户名关联的存储库,而存储库列表仍在被获取.
getByText(“存储库.loadingText');
期望(queryByText(存储库.empty')).toBeNull();
Note that the getBy
query前缀用于断言可以找到DOM元素和 queryBy
查询前缀对于相反的断言很有用. Also, queryBy
如果没有找到任何元素,不返回错误.
Next, 我们要确保, eventually, 应用程序完成获取存储库并将其显示给用户.
await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => {
elementsToWaitFor.推动(getByText(存储库.name));
elementsToWaitFor.推动(getByText(存储库.description));
返回elementsToWaitFor;
}, []));
The waitForElement
异步方法用于等待DOM更新,该更新将把作为方法参数提供的断言呈现为true. In this case, 我们断言,应用程序会显示由模拟GitHub API返回的每个存储库的名称和描述.
Finally, 应用程序不应该再显示存储库正在被获取的指示符,也不应该再显示错误消息.
期望(queryByText(存储库.loadingText')).toBeNull();
期望(queryByText(存储库.error')).toBeNull();
我们得到的React集成测试是这样的:
it('user can view the list of public repositories for entered GitHub username', async () => {
const { getByText, getByPlaceholderText, queryByText } = render( );
userEvent.类型(getByPlaceholderText (userSelection.usernamePlaceholder”),FAKE_USERNAME_WITH_REPOS); 期望(getByPlaceholderText (userSelection.usernamePlaceholder ')).FAKE_USERNAME_WITH_REPOS toHaveAttribute(“价值”);
userEvent.点击(getByText (userSelection.submitButtonText”).最近的(“按钮”));
getByText(“存储库.header');
getByText(“存储库.loadingText');
期望(queryByText(存储库.empty')).toBeNull();
await waitForElement(() => REPOS_LIST.reduce((elementsToWaitFor, repository) => {
elementsToWaitFor.推动(getByText(存储库.name));
elementsToWaitFor.推动(getByText(存储库.description));
返回elementsToWaitFor;
}, []));
期望(queryByText(存储库.loadingText')).toBeNull();
期望(queryByText(存储库.error')).toBeNull();
});
当用户输入没有关联公共存储库的GitHub用户名时, 应用程序显示相应的消息.
describe('when GitHub user has no public repositories', () => {
它('用户显示一条消息,没有公共存储库输入GitHub用户名'), async () => {
const { getByText, getByPlaceholderText, queryByText } = render( );
userEvent.类型(getByPlaceholderText (userSelection.usernamePlaceholder'), FAKE_USERNAME_WITHOUT_REPOS); 期望(getByPlaceholderText (userSelection.usernamePlaceholder ')).FAKE_USERNAME_WITHOUT_REPOS toHaveAttribute(“价值”);
userEvent.点击(getByText (userSelection.submitButtonText”).最近的(“按钮”));
getByText(“存储库.header');
getByText(“存储库.loadingText');
期望(queryByText(存储库.empty')).toBeNull();
await waitForElement(() => getByText(“存储库.empty'));
期望(queryByText(存储库.error')).toBeNull();
});
});
当用户输入一个不存在的GitHub用户名时,应用程序会显示一条错误消息.
describe('when GitHub user does not exist', () => {
it('user is presented with an error message', async () => {
const { getByText, getByPlaceholderText, queryByText } = render( );
userEvent.类型(getByPlaceholderText (userSelection.usernamePlaceholder'), FAKE_BAD_USERNAME); 期望(getByPlaceholderText (userSelection.usernamePlaceholder ')).FAKE_BAD_USERNAME toHaveAttribute(“价值”);
userEvent.点击(getByText (userSelection.submitButtonText”).最近的(“按钮”));
getByText(“存储库.header');
getByText(“存储库.loadingText');
期望(queryByText(存储库.empty')).toBeNull();
await waitForElement(() => getByText(“存储库.error'));
期望(queryByText(存储库.empty')).toBeNull();
});
});
集成测试确实为React应用程序提供了一个甜蜜点. 这些测试有助于捕获错误并使用 TDD approach 同时,当实现发生变化时,它们不需要维护.
React-testing-library, 在本文中展示, 是编写React集成测试的好工具吗, 因为它允许你像用户一样与应用进行交互,并从用户的角度验证应用的状态和行为.
Hopefully, 这里提供的示例将帮助您开始在新的和现有的React项目上编写集成测试. 完整的示例代码,包括应用程序的实现可以在我的 GitHub.
它是一个使用React库编写的用户界面的自动化测试.
集成测试是自动化测试的一种变体,旨在确保系统的独立组件一起工作以实现用户的目标.
集成测试确保组件一起工作,而单元测试确保每个组件独立工作, 但这并不能保证组件集成中没有问题.
端到端测试是自动化测试的一种变体,旨在确保所有系统一起工作以实现用户的目标.
它是一个用于构建用户界面的很棒的声明式、基于组件的库.
Anton是一名拥有强大技术背景的全栈开发人员. 他专门研究JavaScript,是测试驱动开发的粉丝.
世界级的文章,每周发一次.
世界级的文章,每周发一次.