今天来谈谈 React Testing Library 中笔者常用到的一些功能,React Testing Library 的套件名是 @testing-library/react
,它是奠基在 Testing Library 提供的许多方法上,为 React 的测试提供了更多不同的方法。如果读者写的不是 React,Testing Library 本身也搭配很多不同的框架可以用来撰写测试,例如,Angular、Vue、Svelte、Puppeteer、Cypress 等等。
同样的,笔者不会说明如何使用 react-testing-library 来撰写 React 元件的测试,如果有需要的话,推荐可以看 Youtube 上这系列的教学影片 React Testing Library Tutorial。
在 React Testing Library 中提供了三种用来 query DOM 元素的方法,分别是 getBy
、queryBy
、和 findBy
,在官方文件中用详细的表格来说明这三种方法的差异:
图表资料来自官方网站:About Queries @ testing-library/react
但这张表真正想告诉我们的是什麽呢?这里笔者整理各个方法的使用时机重点如下:
queryBy
:找不到该元素时不会喷错,通常是要用来检查某个元素「不在 DOM 上」时使用findBy
:需要搭配 async/await
时,通常该 DOM 元素不是一开始就 mount 在页面上getBy
:除了上述情况之外,都可以用 getBy,getBy
在找不到该元素时会直接喷错(throw Error)
除了这三个方法的差异外,再来就是开发者要找的是「单一个元素(xxxBy)」或「多个元素(xxxAllBy)」:
xxxAllBy
的方法,但却找到超过一个以上的元素时会喷错xxxAllBy
方法时会回传的是阵列由於 Testing Library 很强调用实际使用者的视角来进行测试,因此它会更偏好开发者使用 DOM 上符合 Accessibility 的元素(例如,role、label)、或者是使用者实际上看得的东西(例如、Text、Title)来找到欲进行测试的元素,而不是透过使用者看不到的 class 或 id 来进行 query。
注:不是不能使用 id 来 query 元素,有些时候只能用 id 或直接用 id 会更有效率,只是以偏好来说,Testing Library 更建议使用符合 Accessibility 方法。
但实务上来说,一般的开发者可能并不清楚每个 HTML 元素所对应的 ARIA role 是什麽,这时候有几个不同的方法可以处理。
第一种方式是使用 Chrome 内建的开发者工具,在 Chrome 的开发者工具,按下「Alt + Shift + P」後,搜寻 show accessibility,接着在 Accessibility 页签下的 Computed Properties 中就会显示改元素的 role 和 name:
如此就可以使用 React Testing Library 提供的 getByRole
方法来选到该元素:
// App.test.tsx
import { render, screen } from './custom-testing-library';
import App from './App';
test('can find the specific text in specific DOM', async () => {
render(<App />);
// 使用 getByRole 方法
const heading = screen.getByRole('heading', {
name: /your current path is \//i,
});
expect(heading).toBeInTheDocument();
});
或者也可以使用 Testing Library 内建的 logRole
API。我们只需要把想检视的 HTML Element 放入 logRole
这个方法中,Testing Library 就会告诉开发者在这个 HTML 元素中有哪些 ARIA 的 role 可以使用。
举例来说,在测试的档案中:
// App.test.tsx
import { logRoles, render, screen } from '@testing-library/react';
test('can find the specific text in specific DOM', async () => {
const { container } = render(<App />);
// 使用 logRoles 来检视某个 HTML Element 所包含的 Accessibility Role
logRoles(container);
// ...
});
在 Terminal 中就可以看到所有这个 DOM 中的 ARIA role 和对应的 name,例如这里包含了两个 role,分别是 banner
和 heading
:
最後一种,应该也是最简单的方式是直接透过 Chrome Extension,在 5 Tips to Perfect React Testing Library Queries 这篇文章中推荐了两个好用的 Chrome Extension,分别如下:
Testing Library: which query @ Chrome Extension
这个套件可以直接把要 query 的元素装成 Testing Library 的写法後,用点右键的方式复制下来:
按下复制後,就可以取得下列程序:
screen.getByRole('heading', { name: /your current path is \//i });
Testing Playground @ Chrome Extension
另一个套件是 Testing Playground,它会在 Chrome 的开发者工具中多一个 Tab,当你选了特定元素後,一样会出现可以复制的程序码,除此之外,最下面还会列出和 Accessible 有关的属性:
这两套都可以方便开发者找到想要的元素。
screen.debug()
这个满实用也蛮多人知道的,基本上就是可以把当前画面的 DOM 显示在 Terminal 中:
import { render, screen } from '@testing-library/react';
test('can find the specific text in specific DOM', async () => {
render(<App />);
screen.debug();
});
此时的 Terminal 会得到如下的结果:
虽然用 screen.debug()
可以看到目前 DOM 的样子,但因为它有行数限制,当 Component 转译出来的 DOM 很多行时,就没有办法看到完整的内容。这时候就可以使用 Testing Library 提供的 prettyDOM
这个方法来把特定的 DOM 元素 console 出来。
为什麽不直接使用 console.log()
就好呢?因为会非常难看。举例来说,现在我们找到了 heading 这个元素,想要把它 console 出来看一下:
// App.test.tsx
import { render, screen } from '@testing-library/react';
test('can find the specific text in specific DOM', async () => {
render(<App />);
const heading = screen.getByRole('heading', {
name: /your current path is \//i,
});
// 使用原本的 console.log
console.log(heading);
});
这时候 console 出来的内容会像这样:
非常难以阅读实际的 DOM 会长什麽样,但如果是先用 Testing Library 提供的 prettyDOM
方法後再执行 console,像是这样:
// App.test.tsx
import { prettyDOM, render, screen } from '@testing-library/react';
test('can find the specific text in specific DOM', async () => {
render(<App />);
const heading = screen.getByRole('heading', {
name: /your current path is \//i,
});
// 先使用 prettyDOM 後再 console
console.log(prettyDOM(heading));
});
这时候 console 出来的结果如下:
是不是容易阅读的多了。
最後,除了使用 Accessible Attribute 或画面上的文字来 query DOM 元素之外,有时还是必须使用 id 的方式来 query DOM,Testing Library 预设可以使用 getByTestId
这个方法来找出在 DOM 元素带有特定 data-testid
的 HTML 元素。
举例来说,可以在想要 query 到的 DOM 元素加上 data-testid="heading"
:
<h1 data-testid="heading">Your current path is {location.pathname}</h1>
这时候在写测试时,就可以使用 getByTestId
这个方法:
import { render, screen } from './custom-testing-library';
import App from './App';
test('can find the specific text in specific DOM', async () => {
render(<App />);
const headingElement = screen.getByTestId('heading');
expect(headingElement).toBeInTheDocument();
});
这麽做虽然很方便,但在 Production 的产品上,总是不希望留下这些 data-testid,一来不太好看,二来实在是给爬虫一个很大的方便。因此,如果希望能在 production 时移除 data-testid 这个属性,在官方文件中提到可以透过 babel 的 babel-plugin-react-remove-properties 来解决,这个 plugin 可以在 bundle 成 production 时,把所有指定的 attribute 的移掉,因此我们也可以利用这个 plugin 来把为了测试而写的 data-testid 移除。
使用时需要在 babel 的设定档(.babelrc
)中加上下列设定:
// .bablerc
{
"env": {
"production": {
"plugins": ["react-remove-properties"]
}
}
}
预设就会把名称是以 data-test
开头的属性都移除,但如果读者不是用预设的 data-testid 作为 query 的属性,或者你想透过这个 babel plugin 移除掉其他的 DOM attribute 也是可以的,方式也很简单,可以直接参考该套件的使用说明。
在这次的铁人赛中,笔者仅用大约一周的时间分享撰写测试时的一些经验,实际上测试能撰写的内容,不论是概念或实务都远超过此,在此次铁人赛中也有多位参赛者是撰写和测试有关的题目,如果读者对於测试想要有更多了解,也欢迎去阅读这些内容。
最後,还是鼓励大家从「为你自己开始写测试」,程序是你写的,而你必须为你写的程序负责。
前言 这是需要调整资料库连线资讯,修改成符合MySQL的格式。 本次也会参考《【Day10】Azur...
虽然 Blazor 不需要用到 JavaScript,但某些已有的 library 还是很方便,不能...
Uptime - 掌握系统的生命徵象 系列文章 (1/4) - 我们要观测的生命徵象是什麽? (2/...
为了让我们的测试看起来乾净,就跟写Code一样, 浅显易懂是原则,所以我们要把握以下两点! DRY(...
动态规划也是一种演算法设计模式,常用来解决最佳化问题。它的方法是将问题(通常是递回地)分解成子问题,...