tl; dr
描述和代码示例,用于共享组件之间的测试。包括有关故事书样板组件的编写测试的信息。
目标:减少测试足迹,同时增加覆盖范围
先决条件知识
为什么在组件之间共享测试?
时间和金钱!
共享测试使您的总体代码库保持干燥,如Don't Repeat Yourself
。
在代码库的不同部分之间共享测试可以有几个好处。如果您有两个或多个取决于相同功能的模块,则编写一次涵盖该功能的测试,然后在模块之间共享这些测试可能很有用。对于UI组件,这意味着共享单元测试,用户交流测试,您可以将它们堆叠在一起以制作深入的E2E测试套件。
测试故事书样板故事
使用Storybook的安装程序将Storybook添加到您的项目中时,安装程序包含三个带有stories
文件的组件,以使您启动。三个样板的故事是:
- 按钮
- 标题,导入按钮
- 页面,导入标题
这些通常是所有框架中相同的组成部分和故事。由于它们相互建立,因此它们是共享测试的绝佳用例。
测试按钮
该按钮是最复杂的,具有三个用于样式的道具,一个用于内容的道具,一个用于用户操作。
在Reusable Storybook Interaction Tests中详细介绍了按钮测试。包含按钮测试的Button.shared-spec.js
导出以下内容:
方法 | 描述 |
---|---|
getElements |
返回要测试的元素 |
ensureElements |
有条件测试元素 |
mouseInteraction |
有条件测试鼠标相互作用 |
keyboardInteraction |
有条件测试键盘交互 |
测试标头
标题内部配置按钮组件并具有自己的道具。下表显示了标头接受的道具:
prop | 描述 |
---|---|
user |
一个带有name 属性的简单对象 |
onLogout |
用户触发Log Out 按钮 | 时调用函数
onLogin |
函数在用户触发Log In 按钮 | 时调用函数
onCreateAccount |
用户触发Sign Up 按钮 | 时调用函数
要从标题收集的元素
将根据道具有条件更改的事情:
元素 | 描述 |
---|---|
buttons |
标题中的所有按钮 |
title |
heading (h1 )元素 |
header |
header 元素 |
header
和title
始终是相同的,因为它们没有被props
更改,但我们还是在测试它们以确保它们存在。
buttons
将根据user
prop。
测试标头测试将运行
-
header
元素是否存在? -
title
元素是否存在? -
title
元素是否包含文本Acme
? - 如果用户已登录:
- 有一个按钮吗?
- 如果用户未登录:
- 有两个按钮吗?
/**
* Ensure elements are present and have the correct attributes/content
*/
export const ensureElements = async (elements, args) => {
await expect(elements.header).toBeTruthy();
await expect(elements.title).toBeTruthy();
await expect(elements.title).toHaveTextContent('Acme');
if (args.user) {
await expect(buttons).toHaveLength(1);
} else {
await expect(buttons).toHaveLength(2);
}
...
}
标头让按钮测试本身
由于按钮组件具有自己的测试,因此标头组件不需要知道按钮的工作方式。它只需要知道该按钮有效。
使用按钮的ensureElements
方法,标头测试每个按钮。
// Import the Button's shared tests
import { ensureElements as buttonEnsureElements } from "./Button.shared-spec";
...
export const ensureElements = async (elements, args) => {
... // Header's tests, see above
if (args.user) {
// Recreates button configuration from Header
await buttonEnsureElements({ button: buttons[0] }, {
label: 'Log out',
size: 'small',
});
} else {
await buttonEnsureElements({ button: buttons[0] }, {
label: 'Log in',
size: 'small',
});
await buttonEnsureElements({ button: buttons[1] }, {
label: 'Sign up',
size: 'small',
primary: true,
});
}
用户交互是由按钮拥有的
标题中唯一的交互式元素是按钮,因此,标头唯一需要测试的是键盘用户如何到达按钮。其他所有内容,包括onLogout
,onLogin
和onCreateAccount
函数是否均由按钮所有。
标题测试可以通过触发tab
userevent本身来获得按钮。
/**
* Test keyboard interaction
*/
export const keyboardInteraction = async (elements, args) => {
const { buttons, header } = elements;
if (args.user) {
// `focusTrap` keeps the user's keyboard within the `header` HTMLElement
await userEvent.tab({ focusTrap: header });
// we expect the `Log Out` button to have focus
await expect(buttons[0]).toHaveFocus();
// Uses Button's keyboard interaction tests
await buttonKeyboardInteraction({ button: buttons[0] }, {
label: 'Log out',
size: 'small',
onClick: args.onLogout,
});
} else {
// again, starts within Header
await userEvent.tab({ focusTrap: header });
// we expect the `Log In` button to have focus
await expect(buttons[0]).toHaveFocus();
// User hits `tab` once more
await userEvent.tab({ focusTrap: header });
// we expect the `Sign Up` button to have focus
await expect(buttons[1]).toHaveFocus();
// Button tests for first button
await buttonKeyboardInteraction({ button: buttons[0] }, {
...
});
// Button tests for second button
await buttonKeyboardInteraction({ button: buttons[1] }, {
...
});
}
}
测试页面
页面组件没有道具,所有页面内容都在代码(TSK TSK)内部,因此我们将使用标题测试来测试页面。
fyi-在某些框架中,页面样板组件依赖于JS框架来控制其状态。当我们在框架之间共享这些测试时,我们将测试JS框架。
。从页面收集的元素
在这种情况下,我们将使用标头的getElements
方法获取我们需要的元素。
// Import the Button's shared tests
import { getElements as headerGetElements } from './Header.shared-spec';
/**
* Extract elements from an HTMLElement
*/
export const getElements = async (canvasElement) => {
const screen = within(canvasElement);
// Header knows how to get it's own elements
const headerElements = await headerGetElements(canvasElement);
return {
// spreads the headerElements into this object
...headerElements,
screen,
};
}
测试页面测试将运行
它将仅使用标头测试:
import {
ensureElements as headerEnsureElements,
mouseInteraction as headerMouseInteraction,
keyboardInteraction as headerKeyboardInteraction
} from './Header.shared-spec';
/**
* Ensure elements are present and have the correct attributes/content
*/
export const ensureElements = async (elements, args, step) => {
await headerEnsureElements(elements, args, step, true);
}
/**
* Test mouse interaction
*/
export const mouseInteraction = async (elements, args, step) => {
await headerMouseInteraction(elements, args, step, true);
}
/**
* Test keyboard interaction
*/
export const keyboardInteraction = async (elements, args, step) => {
await headerKeyboardInteraction(elements, args, step, true);
}
包起来
组件的自我知识是带有共享测试的lynchpin。尽可能深入地,一个组件应该知道可能发生的每种可能发生的变化,以及如何测试每种可能性。
在设计系统中使用它,这应该大大减少测试足迹。只要您编写testing-library
测试,此概念应允许在单位测试中使用shared-spec
测试,此外还可以在故事书故事中使用。
所有事物的效率 - 生产力将遵循!