作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Anton Rublev
Verified Expert in Engineering

Anton是一名拥有强大技术背景的全栈开发人员. 他专门研究JavaScript,是测试驱动开发的粉丝.

Expertise

PREVIOUSLY AT

360insights
Share

集成测试是测试成本和价值之间的平衡点. 在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应用程序进行更改时,集成测试可以帮助保持平和的心态, 因为它们确保组件的组合产生所需的用户体验.

React应用程序集成测试的要求

这里有一些东西 React developers want 编写集成测试时要做的事情:

  • 从用户的角度测试应用程序用例. 用户访问网页上的信息并与可用控件进行交互.
  • 模拟API调用不依赖于API可用性和通过/失败测试的状态.
  • 模拟浏览器api(例如,本地存储),因为它们在测试环境中根本不存在.
  • 断言React DOM状态(浏览器DOM或本地移动环境).

现在,对于一些事情,我们应该试着 avoid 在编写React应用程序集成测试时:

  • 测试实现细节. 实现更改应该只在确实引入了bug的情况下才中断测试.
  • Mock too much. 我们想测试应用程序的所有部分是如何协同工作的.
  • Shallow render. 我们想要测试应用中所有组件的组成,小到最小的组件.

为什么选择React-testing-library?

上述要求使 react-testing-library a great choice, 因为它的主要指导原则是允许React组件以一种类似于人类实际使用它们的方式进行测试.

库,以及它的可选 同伴库,允许我们编写与DOM交互并断言其状态的测试.

Sample App Setup

我们将为其编写示例的应用程序 集成测试 实现一个简单的场景:

  • 用户输入GitHub用户名.
  • 该应用程序显示与输入的用户名相关联的公共存储库列表.

从集成测试的角度来看,上述功能是如何实现的应该是无关的. 然而,为了贴近现实世界的应用程序,应用程序遵循通用 React patterns, hence the app:

  • 是单页应用(SPA).
  • 发出API请求.
  • 具有全局状态管理.
  • 支持国际化.
  • 使用一个React组件库.

可以找到应用程序实现的源代码 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:

  • 在所有测试之前, 模拟GitHub API,在使用特定用户名调用时返回存储库列表.
  • 在每个测试之后,清理测试React DOM,以便每个测试从一个干净的点开始.
  • describe 块指定集成测试用例和流变化.
  • 我们正在测试的流量变化有:
    • 用户输入与公共GitHub存储库相关联的有效用户名.
    • 用户输入没有关联公共GitHub存储库的有效用户名.
    • 用户输入GitHub上不存在的用户名.
  • 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集成测试岩

集成测试确实为React应用程序提供了一个甜蜜点. 这些测试有助于捕获错误并使用 TDD approach 同时,当实现发生变化时,它们不需要维护.

React-testing-library, 在本文中展示, 是编写React集成测试的好工具吗, 因为它允许你像用户一样与应用进行交互,并从用户的角度验证应用的状态和行为.

Hopefully, 这里提供的示例将帮助您开始在新的和现有的React项目上编写集成测试. 完整的示例代码,包括应用程序的实现可以在我的 GitHub.

了解基本知识

  • 什么是React测试?

    它是一个使用React库编写的用户界面的自动化测试.

  • 什么是软件测试中的集成测试?

    集成测试是自动化测试的一种变体,旨在确保系统的独立组件一起工作以实现用户的目标.

  • 单元测试和集成测试的区别是什么?

    集成测试确保组件一起工作,而单元测试确保每个组件独立工作, 但这并不能保证组件集成中没有问题.

  • 什么是端到端测试?

    端到端测试是自动化测试的一种变体,旨在确保所有系统一起工作以实现用户的目标.

  • 为什么要使用React?

    它是一个用于构建用户界面的很棒的声明式、基于组件的库.

聘请Toptal这方面的专家.
Hire Now
Anton Rublev

Anton Rublev

Verified Expert in Engineering

利沃夫,利沃夫州,乌克兰

2019年6月24日成为会员

About the author

Anton是一名拥有强大技术背景的全栈开发人员. 他专门研究JavaScript,是测试驱动开发的粉丝.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

PREVIOUSLY AT

360insights

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal开发者

Join the Toptal® community.