测试框架千千万,我偏爱 vitest

开发 前端
Vitest 由核心Vite团队成员Anthony fu(一个托尼)开发,它建立在Vite之上,但非vite项目其实也可以使用vitest。而vite想必大家都非常熟悉了,它可以快速的启动开发服务器,利用浏览器的ES模块系统,从而加快本地开发速度,这让vite变得十分受欢迎。

前端测试框架千千万,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或者一些上下文影响,十分难以调试,这个时候可能会用到skiponly 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,使用最新特性和技术来保证我们代码的稳定性。

责任编辑:姜华 来源: Tecvan
相关推荐

2024-10-10 17:23:31

2013-04-15 10:12:38

2021-05-14 16:34:53

软件电脑硬件

2019-10-24 07:50:52

勒索病毒勒索软件网络安全

2019-11-17 16:09:45

勒索病毒网络攻击网络安全

2019-08-30 16:22:30

编程语言PythonJava

2023-12-06 09:02:12

Vitest前端

2021-05-17 15:23:07

电脑广告软件

2022-08-09 13:08:27

VitestJest前端

2017-11-02 10:45:21

2023-09-11 15:34:53

2011-09-29 10:24:08

项目方法项目变更项目风险

2014-06-17 14:26:25

Windows 7补丁

2018-01-10 14:57:56

2023-11-27 17:40:45

VitestPlaywright前端

2011-08-25 10:15:54

视频演讲乔布斯

2020-10-29 18:38:39

PythonGitHub代码

2023-01-03 10:30:00

Java工具

2022-08-08 10:09:08

Vitest单元测试
点赞
收藏

51CTO技术栈公众号