如何使用相同的测试图书馆UI测试进行故事书交互测试和Vite的本机测试跑者Vitest。
tl; dr
可以通过使用共享测试文件(*.shared-spec.js
)之间的vitest测试(*.test.js
)和Storybook Stories的play
测试之间共享测试。
替换所有断言:
describe('Meow component', () => {
it('should meow', () => {
const { thing1, thing2, thing3, thing4 } = render(<Meow />);
expect(thing1).toBe('some assertion 1')
expect(thing2).toBe('some assertion 2')
expect(thing3).toBe('some assertion 3')
expect(thing4).toBe('some assertion 4')
})
})
带有共享测试:
describe('Meow component', () => {
it('should meow', () => {
sharedTests(render(<Meow />), Meow.props)
})
})
为什么在故事书和单元测试之间共享测试?
时间和金钱!
...以及覆盖范围报告要求...和信任问题...
- 如果您的连续集成系统不允许安装剧作家或运行浏览器,则在CI中不能使用
test-storybook
来测试您的组件 - 如果您的报道工具不支持Storybook的
play
或smoke
测试,但是您已经在Storybook中编写了交互式测试 - 如果您(或您的开发团队)不愿仅仅依靠故事书作为您的唯一测试套件
- 如果您想要组件测试的真理来源
共享测试以及如何使用它们
保持D.R.Y.因此...有关详细信息,请参见共享测试系列的part 1和part 2。
共享规格测试的基本示例用于vitest
按下一个按钮的基本vitest:
// button.test.js
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';
describe('MeowButton', () => {
const props = {
label: 'meow'
onClick: vi.fn(),
};
it('should have properly configured attributes', async () => {
const { queryByRole } = render(<Button {...props} />)
const button = await queryByRole('button')
await expect(button).toBeTruthy();
await expect(button).toHaveTextContent(props.label);
});
it('should respond to mouse interaction', async () => {
const { queryByRole } = render(<Button {...props} />)
const button = await queryByRole('button')
await userEvent.click(button);
await expect(args.onClick).toHaveBeenCalled();
await expect(args.onClick).toHaveBeenCalledTimes(1);
await args.onClick.mockClear();
});
});
如果您有类似的共享测试方法:
// button.shared-spec.js
import { within, userEvent } from '@storybook/testing-library';
export const getElements = async (canvasElement) => {
const button = await within(canvasElement).findByRole('button');
return { button };
};
export const ensureElements = async (elements, args) => {
const { button } = elements;
// same assertions as in the Vitest
await expect(button).toBeTruthy();
await expect(button).toHaveTextContent(args.label);
};
export const mouseInteraction = async (elements, args) => {
const { button } = elements;
// same user event as in the Vitest
await userEvent.click(button);
// same assertions as in the Vitest
await expect(args.onClick).toHaveBeenCalled();
await expect(args.onClick).toHaveBeenCalledTimes(1);
await args.onClick.mockClear();
}
然后将共享测试方法集成到vitest中,将使您可以集中所有在其他测试系统中使用的断言。您只需要用vitest进行组件渲染的vitest特定容器。
// button.test.js (updated)
import { render } from '@testing-library/react';
import { getElements, ensureElements } from './button.shared-spec';
import { vi } from 'vitest';
describe('MeowButton', () => {
const props = {
label: 'meow'
onClick: vi.fn(),
};
it('should have properly configured attributes', async () => {
const rendered = render(<Button {...props} />)
const elements = await getElements(rendered.container);
await ensureElements(elements, props);
});
it('should respond to mouse interaction', async () => {
const rendered = render(<Button {...props} />)
const elements = await getElements(rendered.container);
await mouseInteraction(elements, props);
});
});
作为故事书中共享测试的复习 - 以下是故事书故事中的共享测试方法。
// button.stories.js
import { getElements, ensureElements } from './button.shared-spec';
export const MeowButton = {
args: {
label: 'meow',
},
play: async ({ args, canvasElement }) => {
const elements = await getElements(canvasElement);
await ensureElements(elements, args);
await mouseInteraction(elements, args);
},
};
重要点
-
rendered.container
等于canvasElement
,因为它们都是包含渲染组件的HTMLElement
-
getElements
,mouseInteraction
和ensureElements
与Storybook的play
函数中使用的功能相同
通过共享测试套件扩展概念!
虽然上一个示例似乎过于建造,但是当您有很多测试可以为许多不同的情况写作时,这个概念是疯狂的。使它更容易的一种方法是在test
文件中创建共享的测试套件方法。
// button.test.js (updated)
/**
* Uses the shared spec methods inside vitest test functions
* @param Component - OG component or a composed-via-storybook version
* @param args - props to pass to the component
*/
const buttonTestSuite = (Component, args) => {
it('should have properly configured attributes', async () => {
const rendered = render(<Component {...args} />)
const elements = await getElements(rendered.container);
await ensureElements(elements, args);
});
it('should respond to mouse interaction', async () => {
const rendered = render(<Component {...args} />)
const elements = await getElements(rendered.container);
await mouseInteraction(elements, args);
});
it('should respond to keyboard interaction', async () => {
const rendered = render(<Component {...args} />)
const elements = await getElements(rendered.container);
await keyboardInteraction(elements, args);
});
}
describe('Button', () => {
describe('Primary', () => {
buttonTestSuite(Primary, Primary.args);
});
describe('Secondary', () => {
buttonTestSuite(Secondary, Secondary.args);
});
describe('Large', () => {
buttonTestSuite(Large, Large.args);
});
describe('Small', () => {
buttonTestSuite(Small, Small.args);
});
});
以上将以相同的方式测试按钮的每个排列,您可以根据需要添加尽可能多的测试示例,而不必重复自己或过度增加文件的大小。
详细说明覆盖范围看起来像这样:
✓ src/stories/Button.test.jsx (12)
✓ Button (12)
✓ Primary (3)
✓ should have properly configured attributes
✓ should respond to mouse interaction
✓ should respond to keyboard interaction
✓ Secondary (3)
✓ should have properly configured attributes
✓ should respond to mouse interaction
✓ should respond to keyboard interaction
✓ Large (3)
✓ should have properly configured attributes
✓ should respond to mouse interaction
✓ should respond to keyboard interaction
✓ Small (3)
✓ should have properly configured attributes
✓ should respond to mouse interaction
✓ should respond to keyboard interaction
Test Files 1 passed (1)
Tests 12 passed (12)
Start at 16:07:51
Duration 1.38s (transform 188ms, setup 159ms, collect 293ms, tests 118ms, environment 345ms, prepare 85ms)
结论
Storybook和您的应用程序的测试套件之间的共享测试可以看作是一个需要的小众问题...但是,以更具程序性的方式看待您的测试将:
- 保持考试套房干燥
- 允许您的测试断言的单一真实来源
- 允许您更改UI框架,但要保留相同的UX (请在第4部分中解释,在JavaScript Frameworks之间共享测试))