前端测试框架千千万,jest、vitest、Jasmine、mocha等等,但我个人更偏爱 vitest,简洁,高效,现代化,这篇文章就带大家了解一下关于vitest的一些知识和特性,帮助大家更好的使用vitest。
why vitest
Vitest 由核心Vite团队成员Anthony fu(一个托尼)开发,它建立在Vite之上,但非vite项目其实也可以使用vitest。而vite想必大家都非常熟悉了,它可以快速的启动开发服务器,利用浏览器的ES模块系统,从而加快本地开发速度,这让vite变得十分受欢迎。
虽然用vite开发的项目也可以和jest一起使用,但是vite和jest中其实有很多配置重复的部分,迫使用户要维护和运行两套流程。
所以,vitest一开始要解决的问题就是便于在vite项目中方便集成测试能力;在开发中,将Vitest与Vite结合使用的主要优势是他们都有很好的性能,方便一起集成,以及他们都共享配置同一份配置。
vite的爆火一定程度上也让vitest增加了曝光度,它也在社区中十分受欢迎,成为了越来越多测试框架的首选:
Vitest 对比 jest
说到vitest,不得不提另外一个测试框架也就是jest。
早在2011年,还没有JavaScript测试框架能满足Facebook(meta)团队的测试需求,所以他们创建了Jest。Jest在2014年开源,就在React开源后不久,jest也随着React迅速流行起来。2016年,jest成为create react APP(CRA)命令创建react应用的首选测试框架,Next.js V12版本也把jest作为默认测试框架,jest主流地位已然确立。
2022年,jest所有权已经转移给OpenJS基金会,由meta外部人员维护,之后jest的更新和维护就变得相对缓慢。jest的最新一次更新版本在1年前,jest的v30版本早在2023年就已经发布alpha版本,但是现在已经2025年了,v30版本仍然没有发布;但是vitest去年就从v1版本更新到了v3版本......
vitest对比jest一个显著的优势是vitest原生支持typescript、ESM,而jest v29版本现在对于ESM的支持还是实验性的,此外如果要支持typescript还需要配置babel,通过babel来转译支持typescript。而vitest对于现代特性都是开箱即用的。
关于性能方面,在redit论坛 (https://www.reddit.com/r/reactjs/comments/10zyse3/is_jest_still_faster_than_vitest/)上有很多关于jest和vitest的性能测试的讨论,有的反馈说jest比较快,有的说vitest比较快。但不管怎么样,vitest的HMR特性是要比jest是要快的,也就是开发过程中只运行修改文件的测试。原因是vitest是基于修改模块的依赖树去做的分析重新运行,而jest是基于git未提交代码做的分析,这可能不太准确,因为并非所有检测到的更改文件都与现在运行的测试相关。
下面是关于jest和vitest特性的一个全面对比:
vitest有更多的现代特性,以及vitest处于一个快速迭代社区逐渐繁荣的阶段,如果是一个使用现代特性的新项目,那么vitest肯定是首选!
vitest的一些特性
关于怎么开始和配置,比较简单,本文就不展开了,下面详细聊聊vitest的一些特性,了解完这些特性后,想必你对vitest有更全面的认识。
兼容jest API 但是更强
因为jest的API已经设计的比较优秀(从他的下载占有率可以看出),vitest几乎完全兼容了jest的API设置。比如Expect、Mock、Setup and TearDown等API。所以从jest项目迁移到vitest只需要做很少的调整,具体可以参考官方文档(https://vitest.dev/guide/migration.html#jest),其中一个都会遇到的调整是globals的配置,他可以不用引入vitest,也能使用全局API。
此外,vitest的v3 API增加了更多的能力,比如toHaveBeenCalledExactlyOnceWith,他可以检测某个函数只被调用了一次且调用参数为预期值。
test('vitest mocking example', () => {
const mock = vi.fn();
mock('hello');
// New matcher for more precise assertions
expect(mock).toHaveBeenCalledExactlyOnceWith('hello');
// Spy reuse example
const obj = {
method: () => {}
};
const spy = vi.spyOn(obj, 'method');
// Spy is automatically reused if spyOn is called again
const sameSpy = vi.spyOn(obj, 'method');
expect(spy === sameSpy).toBe(true);
});
默认watch模式
在jest中,我们要使用watch模式的时候,需要增加--watch参数(即jest --watch);但是vitest默认使用watch模式(即vitest),不需要增加--watch参数,也能自动进入watch模式。
但是如果是CI环境,我们运行watch模式,是不是不符合预期?
vitest会根据是否有环境变量process.env.CI判断是否处于CI环境,如果有环境变量则不会启动watch模式。
当然也可以通过指定--watch参数以及--run参数,来指定watch模式还是单次运行。
源码内联测试
什么是源码内联测试(In-Source Testing)呢?
这是一个rust模块测试的方式,他可以让我们把测试代码写在源码中。
这可以让测试代码和源码代码使用相同的闭包环境,关注的逻辑和代码在同一地方,这可以让我们更快速的发现问题、修复问题,每次代码修改后都能立即运行测试代码并进行修复,便于维护;但是由于测试代码和源代码在一个文件里,非常容易耦合,所以对代码质量有一定要求。
下面是一个内联代码测试的demo:
export function add(...args: number[]) {
return args.reduce((a, b) => a + b, 0)
}
// in-source test suites
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest
it('add', () => {
expect(add()).toBe(0)
expect(add(1)).toBe(1)
expect(add(1, 2, 3)).toBe(6)
})
}
官方建议对更复杂的测试使用单独的测试文件,源代码内测试适用于对小函数进行单元测试和原型设计,因此对于为 JavaScript 库编写测试特别有用。
此外,如果我们在生产环境使用该模式,应该为bundler增加变量定义,以便于bundler分析dead code并删除:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
includeSource: ['src/**/*.{js,ts}'],
},
define: {
'import.meta.vitest': 'undefined',
},
})
真正的浏览器测试
在vitest v2版本引入了浏览器模式,该模式还处于实验阶段。该模式将在浏览器环境直接运行代码,所以他可以访问真正的dom API,所以理论上在实际环境中运行的测试将会更加可靠。缺点就是慢!
在node环境的测试中,我们一般通过js-dom或者happy-dom来模拟浏览器环境,happy-dom支持更少的浏览器API,但是更快,js-dom则相反。我们可以根据需要选择不同的dom模拟环境,vitest同时支持js-dom和happy-dom。
vitest支持在不同的容器运行测试,比如playwright或者webdriverio ,这两者比较显著的差异是他们底层协议不同,playwright使用的Chrome DevTools 协议,而webdriverio使用的是WebDriver 协议:
vitest提供了开箱即用的包来渲染常用的前端框架的组件,下面是一个react的demo:
import { expect, test } from'vitest'
import { render } from'vitest-browser-react'
import HelloWorld from'../src/HelloWorld'
test('renders name', async () => {
const { getByText, getByRole } = render(<HelloWorld name="Vitest" />)
await expect.element(getByText('Hello Vitest x1!')).toBeInTheDocument()
await getByRole('button', { name: 'Increment '}).click()
await expect.element(getByText('Hello Vitest x2!')).toBeInTheDocument()
})
可以看到API和用法几乎和@testing-library/react几乎相同,相同的代码可以让运行环境切换到浏览器环境!
其他的特性
- Browber UI。直接启动一个前端页面,在这个页面中可以更好的调试、运行、修复测试;
- 类型测试。提供类型断言的API可以对TS的类型进行测试;
- 提供benchmark
- workspace配置。v3版本的vitest增加了workspace配置,它特别适用于monorepo,可以在一个配置文件里配置多个项目的配置。
- ...
这里就不一一列举了,感兴趣的小伙伴建议直接查看官网。
一些技巧
下面分享个人在使用vitest的过程中的一些有用的技巧。
使用vscode的debug terminal
这是一个node调试比较通用的技巧,对于vitest同样适用。使用vscode 的debug terminal可以非常方便的查看变量的参数,这非常适合我们在mock出现问题的时候debug。
使用skip或者only来快速调试
在调试单元测试的时候,我们经常会遇到单测报错,但是可能因为mock或者一些上下文影响,十分难以调试,这个时候可能会用到skip
和only
API,这两个API可以只运行某个case或者跳过某个case,可以减少影响面,帮助我们快速调试(而不是注释代码)
it.only('should be open', () => {
//...
});
it.skip('should be closed', () => {
//...
});
优雅的retry
vitest中有很多重试的方法,比如:
import { expect, test } from"vitest";
import { sum } from"./sum.js";
it(
"adds 1 + 2 to equal 3",
{
retry: 3,
timeout: 3000,
},
() => {
expect(sum(1, 2)).matchSnapshot();
}
);
it支持传入第二个配置参数,该参数可以配置retry和timeout等,可以针对一整个case retry的场景。
此外,vitest还支持waifor API,该API可以不断尝试和获取API:
// @vitest-environment jsdom
import { expect, test, vi } from'vitest'
import { getDOMElementAsync, populateDOMAsync } from'./dom.js'
test('Element exists in a DOM', async () => {
// start populating DOM
populateDOMAsync()
const element = await vi.waitFor(async () => {
// try to get the element until it exists
const element = await getDOMElementAsync() as HTMLElement | null
expect(element).toBeTruthy()
expect(element.dataset.initialized).toBeTruthy()
return element
}, {
timeout: 500, // default is 1000
interval: 20, // default is 50
})
expect(element).toBeInstanceOf(HTMLElement)
})
上面这个case会在dom真正渲染出来,才会执行下面的语句,之前会一直轮询执行语句,判断是否正确执行。
另外,@testing-library/react里面也有很多类似的API底层其实就是使用waitfor,例如findAllByTextAPI,该API会等待元素渲染完成之后,再继续进行下面的代码。
Bad case:
使用尽量详细的断言
我遇到一些单测的case,因为没有运行每个case前mockReset,导致之前spy函数调用记录仍然存在,于是好几个case在不同的测试场景中,相同的断言均是toHaveBeenCalledWith,该断言因为只会判断历史调用中是否存在某参数调用就行了,所以导致这几个case均成功了,但是实际上是有问题的。
我们应该使用更加详细的断言,比如toHaveBeenNthCalledWith或者上文提到的toHaveBeenCalledExactlyOnceWith。
总结
vitest能够快速火起来,我认为其原因有三点。首先提供了和jest兼容的API设计,这一点对于开源工具来说十分重要,让用户可以更少的迁移成本;第二个是拥抱社区,提供了很多很现代的功能,比如浏览器模式、源码内联测试;第三个基于vite的设计理念,使用现代的API和工具,让开发者开箱即用ESM和typescript,无需任何其他配置,提供更好的开发者体验。
我们应该关注vitest最新特性,及时升级vitest,使用最新特性和技术来保证我们代码的稳定性。