Vitest 单元测试方案
创作时间:
作者:
@小白创作中心
Vitest 单元测试方案
引用
CSDN
1.
https://blog.csdn.net/hlsxjh/article/details/143647047
Vitest 单元测试方案
Vitest 是一个面向 Vite 的极快的单元测试框架。它利用了 Vite 的优势,提供了一种全新的测试体验。本文将介绍如何在项目中集成和使用 Vitest 进行单元测试。
安装 Vitest
npm install -D vitest
配置 Vitest
在项目根目录下创建 vitest.config.js
文件,用于配置 Vitest。
import path from 'path';
export default {
test: {
globals: true,
environment: 'happy-dom',
restoreMocks: true,
include: ['**/*.{test,spec,type-test}.{js,mjs,cjs,ts,tsx,jsx}'],
coverage: {
exclude: ['tests/**', '.eslintrc.cjs'],
reporter: ['cobertura', 'text', 'html', 'clover', 'json'],
},
setupFiles: `${path.resolve(__dirname, 'tests/setup.ts')}`,
snapshotFormat: {
printBasicPrototype: true,
},
alias: [],
},
};
配置 Vitest公共文件
在项目根目录下创建 setup-test.ts
文件,用于统一处理定时器,原生事件,以及在vitest环境下无法模拟的事件
import { vi, describe, it, expect } from 'vitest';
import '@testing-library/jest-dom/vitest';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import { configure } from '.';
export const mock = new MockAdapter(axios);
configure({
renderMode: 'global',
enableMultipleSelection: false,
});
beforeEach(() => {
mock.reset();
console.error = vi.fn();
console.warn = vi.fn();
vi.useFakeTimers();
window.requestIdleCallback = vi.fn();
document.execCommand = vi.fn();
window.onresize = vi.fn();
vi.setSystemTime(new Date(1680278400000));
});
afterEach(() => {
mock.reset();
vi.useRealTimers();
});
window.vi = vi;
window.describe = describe;
window.it = it;
window.expect = expect;
单元测试分类
- 单元测试:主要是对项目里的最小组件或模块进行测试,一般是在非浏览器环境下的测试
- 集成测试:一般是对单元测试下的多个最小单元组成的较大组件或模块进行小型的组合测试,一般是在非浏览器环境下的测试。
- 端到端测试 (E2E) : 从用户的视角出发,基于整个页面或者应用,模拟用户操作测试,一般是在浏览器环境下的测试
如何编写单元测试
在项目 src
目录平级的位置,新建 tests
文件夹,tests
文件夹下新建 mocks
文件夹
mocks
下主要存放对应单元测试模拟数据的文件tests
下主要存放对应的单元测试文件
如何模拟接口请求
在项目中引入 axios-mock-adapter
第三方工具库,在进行测试的 test
文件下新建 Mock
文件夹,然后新建 test-demo-mock.ts
文件。
import { mock } from '../../../src/setup-test';
export const TestDemoMock = {
getUsersTitle: (status: number) => {
mock.onGet('/users/title').reply(status, { title: '江雾' });
},
};
编写测试用例
新建一个 test-demo.tsx
文件,编写React组件
import axios from 'axios';
import { useEffect, useState } from 'react';
const TextDemo: React.FC = () => {
const [title, setTitle] = useState('测试');
const getTitleValue = async () => {
const response = await axios.get('/users/title');
const { status, data } = response;
if (status === 200) {
setTitle(data.title);
} else {
// error
setTitle('接口异常');
}
};
const changeBtn = () => {
setTitle('江雾');
};
return (
<div className="test-dome-content">
<span className="title">{title}</span>
<button className="refresh-button" onClick={getTitleValue}>
refresh
</button>
<button onClick={changeBtn} className="change-btn">
change
</button>
</div>
);
};
export default TextDemo;
新建 test-demo.test.ts
单元测试文件
import { fireEvent, render } from '@testing-library/react';
import TextDemo from '../../src/test-demo';
import { TestDemoMock } from './Mock/test-demo-mock';
describe('getUser', () => {
it('mount title', async () => {
const { container } = render(<TextDemo />);
// 初始化期望 页面标题为测试
const title = document.body.querySelector('.test-dome-content .title');
expect(title?.innerHTML).toBe('测试');
});
it('change title', async () => {
await TestDemoMock.getUsersTitle(200);
render(<TextDemo />);
// 初始化期望 页面标题为测试
const title = document.body.querySelector('.test-dome-content .title');
expect(title?.innerHTML).toBe('测试');
// 点击按钮
const btn = document.body.querySelector(
'.test-dome-content .refresh-button'
);
fireEvent.click(btn!);
await vi.waitFor(() => {
const targetTitle = document.body.querySelector(
'.test-dome-content .title'
);
expect(targetTitle?.innerHTML).toBe('江雾');
});
});
it('change title error', async () => {
// 模拟接口请求
await TestDemoMock.getUsersTitle(201);
render(<TextDemo />);
// 初始化期望 页面标题为测试
const title = document.body.querySelector('.test-dome-content .title');
expect(title?.innerHTML).toBe('测试');
// 点击按钮
const btn = document.body.querySelector(
'.test-dome-content .refresh-button'
);
fireEvent.click(btn!);
// 期望接口失败内容为接口异常
await vi.waitFor(() => {
const title = document.body.querySelector('.test-dome-content .title');
expect(title?.innerHTML).toBe('接口异常');
});
});
});
常见的命令
pnpm run test
: 执行所有单元测试pnpm test xxx.test.ts
: 执行某个单元测试文件pnpm run coverage
: 覆盖率报告
代码覆盖率报告
执行 pnpm run coverage
会生成 coverage
文件夹,将其中的 index.html
拖入浏览器中,查看具体的单元测试代码覆盖详情
E2E快速补齐单测方案
自动化录制解决方案:
- Chrome 插件 DeploySentinel Recorder
- Chrome 插件 Headless Recorder
- Playwrighg 官方的 CLI Playwright CLI Codegen
- eTest 插件
热门推荐
揭秘华为13条人才观与人才战略:保持组织活力是核心
慢跑时膝盖为何会痛?从肌肉协调到跑姿的全面解析
温泉度假村设计:如何打造极致舒适体验
火花塞更换周期:铱金和镍合金到底差了几万公里?
什么牌子的火花塞性能最好,火花塞品牌有哪些
青岛胶东国际机场最全出行攻略:10种交通方式任你选
八字【时柱】推断
名侦探柯南:从创作背景到人物设定的全方位解析
亨利定律对混合油品馏程的影响
《抓娃娃》引热议:吃苦教育,真的能培养出好孩子?
高要区回龙镇成肇庆市唯一上榜省文化旅游特色镇
寓言故事和成语故事
长期不写字的人大脑正在退化!拿笔写字比手机打字强在哪?
图片懒加载的四种实现方案
20分钟快手早餐:提前准备,营养美味两不误
“FSR+AFMF”双管齐下,看RX 6750 GRE如何畅玩《黑神话:悟空》
装修地漏怎么选?你不得不知道的地漏知识
团队领导者如何做好自己
培养基的种类及其应用
被狗咬伤,只打疫苗可不够,伤口还需这样处理!
怎么吃才补钙?这 3 种方法,简单有效
2024春季皮鞋穿搭指南:多种场合、不同皮鞋的搭配灵感,女生必须学起来!
颈部淋巴结肿大?炎症还是肿瘤?如何辨别及应对方法详解!
高价买进低价卖出的原因是什么?如何避免这种情况?
还不知道这个原则的程序员,要小心了
博德之门3 暗夜之歌:探索神秘剧情与角色深度
狐妖小红娘涂山苏苏和涂山红红是一个人吗
天津大学工程力学拔尖人才培养计划:面向未来的力学精英摇篮
工程力学专业就业方向及前景!附2024大学排名及分数线
奥美迦奥特曼初期形态曝光:白虎青龙双装甲亮相