Skip to main content

Vue ㄨ Vitest 测试组件

一、安装及配置

1、vitest

yarn add -D vitest

package.json 中添加:

package.json
{
"scripts": {
"test": "vitest"
}
}

Vitest 使用 test、describe、it 等 Jest API 时需要单独 import,可在 test 配置中设置 global: true,之后无需 import 就能在文件中使用这些 API:

vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vite'
// ...

export default defineConfig({
// ...
test: {
globals: true,
},
})

注意 vite.config.ts 中配置 test 需要将三斜线指令写在文件顶部。

2、vue/test-utils

在编写单元测试时,用一个假组件来替换组件的现有实现,被称为 stub(存根),为了在测试中使用存根,需要用到官方测试工具库 Vue Test Utils(类似 @testing-library/react)的 mount 等方法。安装如下:

yarn add --dev @vue/test-utils@next

3、jsdom

Vitest 的默认测试环境是 Node.js。可以使用 jsdomhappy-dom 这种类似浏览器(browser-like)的环境来替代 Node.js。安装如下:

yarn add --dev jsdom

可以在单个测试文件的顶部添加注释来指定测试环境:

index.test.tsx
/**
* @vitest-environment jsdom
*/

// ...

也可以在配置文件中全局配置测试环境(建议):

vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vite'
// ...

export default defineConfig({
// ...
test: {
globals: true,
environment: 'jsdom',
},
})

4、兼容 Vue JSX

当项目中使用 Vue JSX 时,需要进行如下配置以使 .tsx / .jsx 转换为客户端组件:

vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from 'vite'
// ...

export default defineConfig({
// ...
test: {
globals: true,
environment: 'jsdom',
transformMode: {
web: [/\.[jt]sx$/]
}
},
})

二、Jest〡Vitest 测试用例对照

1、生成测试快照

index.test.tsx
import { mount } from "@vue/test-utils";
import { Button } from '../index';

describe('Button 组件测试', () => {
it('create', () => {
const wrapper = mount({
render() {
return <Button>foo</Button>;
},
});
expect(wrapper.element).toMatchSnapshot();
});
});

2、校验查询到的子元素

2-1、匹配指定类名元素是否存在

index.test.tsx
import { mount } from "@vue/test-utils";
import { Alert } from '../index';

describe('Alert 组件测试', () => {
it('closable', () => {
const wrapper = mount({
render() {
return <Alert message="这是一条消息提示" closable />;
},
});
wrapper.find('.i-alert--close-btn').trigger('click');
expect(wrapper.find('.i-alert').exists()).toBe(false);
})
});

2-2、匹配元素的文本是否正确

index.test.tsx
import { mount } from "@vue/test-utils";
import { Button } from '../index';

describe('Button 组件测试', () => {
it('children', () => {
const wrapper = mount({
render() {
return <Button>foo</Button>;
},
});
expect(wrapper.find('.i-button').text()).toBe('foo');
});
});

2-3、匹配指定属性元素是否存在

index.test.tsx
import { mount } from "@vue/test-utils";
import { Xx } from '../index';

describe('Alert 组件测试', () => {
it('children', () => {
const wrapper = mount({
render() {
return (
<Breadcrumb>
<Breadcrumb.Item>item1</Breadcrumb.Item>
<Breadcrumb.Item maxWidth={80} data-testid='test-item'>item2</Breadcrumb.Item>
<Breadcrumb.Item>item3</Breadcrumb.Item>
</Breadcrumb>
);
},
});
expect(wrapper.find('[data-testid="test-item"]').attributes('style')).toMatch('max-width: 80px;');
});
});

3、根据传入属性校验类名

index.test.tsx
import { mount } from "@vue/test-utils";
import { Avatar } from '../index';

describe('Avatar 组件测试', () => {
it('shape', () => {
const wrapper = mount({
render() {
return <Avatar shape="round">L</Avatar>;
},
});
expect(wrapper.classes()).toContain('i-avatar__shape-round');
});
});

4、根据传入属性校验样式

注意

vue/test-utils 的 attributes〡getAttribute 会把测试的颜色值转为 rgb 格式。

index.test.tsx
import { mount } from "@vue/test-utils";
import { Avatar } from '../index';

describe('Avatar 组件测试', () => {
it('size', () => {
const wrapper = mount({
render() {
return <Avatar size={24}>L</Avatar>;
},
});
expect(wrapper.element.getAttribute('style')).toMatch('width: 24px;');
// 或
expect(wrapper.find('.i-avatar').attributes('style')).toMatch('width: 24px;');
});
});

5、测试传入事件是否生效

index.test.tsx
import { mount } from "@vue/test-utils";
import { vi } from 'vitest';
import { Button } from '../index';

describe('Button 组件测试', () => {
it('onClick', () => {
const clickFn = vi.fn();
const wrapper = mount({
render() {
return <Button onClick={clickFn} />;
},
});
wrapper.findComponent(Button).trigger('click');
expect(clickFn).toBeCalledTimes(1);
// 或
// expect(clickFn).toHaveBeenCalled();
});
});

6、测试禁用事件是否生效

index.test.tsx
import { mount } from "@vue/test-utils";
import { vi } from 'vitest';
import { Button } from '../index';

describe('Button 组件测试', () => {
it('disabled', () => {
const clickFn = vi.fn();
const wrapper = mount({
render() {
return <Button disabled onClick={clickFn} />;
},
});
expect(wrapper.classes()).toContain('i-button-disabled');
wrapper.findComponent(Button).trigger('click');
expect(clickFn).toBeCalledTimes(0);
});
});

更多测试用例可参考 iDesign Vue 代码。