Vitest 简介 
由 Vite 提供支持的极速原生的单元测试框架。安装 pnpm add -D vitest,Vitest 需要 Vite >=v3.0.0 和 Node >=v14。
- Vite 支持 
 重复使用 Vite 的配置、转换器、解析器和插件 - 在您的应用程序和测试中保持一致。
- 兼容 Jest 
 拥有预期、快照、覆盖等 - 从 Jest 迁移很简单。
- 智能即时浏览模式 
 智能文件监听模式,就像是测试的 HMR!
- ESM, TypeScript, JSX 
 由 esbuild 提供的开箱即用 ESM、TypeScript 和 JSX 支持。
Vitest 跟其他的测试框架进行对比 
Vitest 和基于浏览器的运行器之间的主要区别是速度和执行上下文。简而言之,基于浏览器的运行器,如 Cypress,可以捕捉到基于 Node 的运行器(如 Vitest)所不能捕捉的问题(比如样式问题、原生 DOM 事件、Cookies、本地存储和网络故障),但基于浏览器的运行器比 Vitest 慢几个数量级,因为它们要执行打开浏览器,编译样式表以及其他步骤。
Jest 
Jest 通过为大多数的 JavaScript 项目提供了开箱即用的测试支持,填补了测试框架的空白,有着舒适的 API(例如 it 和 expect),以及大多数所需要的全套测试功能(例如快照,对象模拟,代码测试覆盖率)。
如果你的项目由 Vite 驱动,Jest 和 Vite 之间有很多重复的部分,让用户不得不创建两个不同的配置文件。配置和维护两个不同的容器是一件极其不合理的操作。使用 Vitest,你就可以将开发,构建和测试环境的配置定义为单个容器,共享相同的插件和 vite.config.js。同时可以通过相同的插件 API 进行扩展,与 Vite 形成完美的集成。
由于 Jest 的使用规模,Vitest 提供了与之兼容的 API,允许在大多数项目中将其作为备选使用,同时还包括了单元测试时最常见的功能(模拟,快照以及覆盖率)。Vitest 与大多数 Jest API 和生态系统库都有较好的兼容性,因此在大多数项目中,可以无缝的将 Jest 替换成 Vitest 。
Cypress 
Cypress 是著名的端到端测试工具,是基于浏览器的测试工具,是 Vitest 的补充工具之一。
基于浏览器运行测试的框架(Cypress、Web Test),会捕获到 Vitest 无法捕获的问题,因为他们都是使用真实的浏览器和 APIs。相比之下,Vitest 专注于为非浏览器逻辑提供最佳的开发体验。如果你想使用 Cypress,建议将 Vitest 用于测试项目中非浏览器逻辑,将 Cypress 用于测试依赖浏览器的逻辑。
Cypress 更像是一个 IDE 而不是测试框架,因为您还可以在浏览器中看到真实呈现的组件,以及它的测试结果和日志。Cypress 还尝试将 Vite 集成进他们自己的产品中:使用 Vitesse 重新构建他们的应用程序的 UI,并使用 Vite 来测试驱动他们项目的开发。
Vitest 支持各种实现部分浏览器环境的第三方包,例如 jsdom,可以让我们快速的对于任何引用浏览器 APIs 的代码进行单元测试。但这些浏览器环境在实现上有局限性,例如 jsdom 缺少相当数量的特性,诸如 window.navigation 或者布局引擎(offsetTop 等)。
Cypress 不是对业务代码进行单元测试的好选择,但使用 Cypress(用于端对端和组件测试)配合 Vitest(用于非浏览器逻辑的单元测试)将满足你应用程序的测试需求。
Vitest 主要功能 
- 与 Vite 通用的配置、转换器、解析器和插件。
- 使用你的应用程序中的相同配置来进行测试!
- 智能文件监听模式(默认启用),就像是测试的 HMR!
- 支持测试 Vue、React、Lit 等框架中的组件。
- 开箱即用的 ES Module / TypeScript / JSX support / PostCSS
- ESM 优先,支持模块顶级 await
- 注重性能,通过 tinypool 使用 Worker 线程尽可能多地并发运行
- 使用 Tinybench 来支持基准测试
- 套件和测试的过滤、超时、并发配置
- Jest 的快照功能
- 内置 Chai 进行断言 + 与 Jest expect 语法兼容的 API
- 内置用于对象模拟(Mock)的 Tinyspy
- 使用 jsdom 或 happy-dom 用于 DOM 模拟
- 通过 c8 来输出代码测试覆盖率
- 类似于 Rust 语言的 源码内联测试
- 通过 expect-type 进行类型测试
测试环境 
Vitest 提供 environment 选项以在特定环境中运行代码,可以使用 environmentOptions 选项修改环境的行为方式。默认情况下,可以使用这些环境:
- node为默认环境
- jsdom通过提供 Browser API 模拟浏览器环境,使用- jsdom包
- happy-dom通过提供 Browser API 模拟浏览器环境,被认为比 jsdom 更快,但缺少一些 API,使用- happy-dom包
- edge-runtime模拟 Vercel 的 edge-runtime,使用- @edge-runtime/vm包
- 设置 environment选项时,它将应用于项目中的所有测试文件。要获得更细粒度的控制,可以使用控制注释为特定文件指定环境,以@vitest-environment开头,后跟环境名称的注释:
// @vitest-environment jsdom
import { test } from 'vitest'
test('test', () => {
  expect(typeof window).not.toBe('undefined')
})也可以通过设置 environmentMatchGlobs 选项,根据 glob 模式指定环境。
- 从 0.23.0 开始,你可以创建自己的包,名为 vitest-environment-${name},来扩展 Vitest 环境。该包应导出一个具有Environment属性的对象:
import type { Environment } from 'vitest'
export default <Environment>{
  name: 'custom',
  setup() {
    // custom setup
    return {
      teardown() {
        // called after all tests with this env have been run
      },
    }
  },
}可以通过 vitest/environments 访问默认的 Vitest 环境:
import { builtinEnvironments, populateGlobal } from 'vitest/environments'
console.log(builtinEnvironments) // { jsdom, happy-dom, node, edge-runtime }populateGlobal 实用函数用于将属性从对象移动到全局命名空间。
测试上下文 
Vitest 的测试上下文允许你定义可在测试中使用的工具(utils)、状态(states)和固定装置(fixtures)。
- 每个测试回调的第一个参数是测试上下文。
import { it } from 'vitest'
it('should work', (ctx) => {
  // prints name of the test
  console.log(ctx.meta.name)
})- 内置的测试上下文。
- context.meta
 包含关于测试的元数据的只读对象。
- context.expect
 绑定到当前测试的 expect API。
- 每个测试的上下文都不同。可以在 beforeEach和afterEachhooks 中访问和扩展它们。
import { beforeEach, it } from 'vitest'
beforeEach(async (context) => {
  // extend context
  context.foo = 'bar'
})
it('should work', ({ foo }) => {
  console.log(foo) // 'bar'
})- 可以通过添加聚合(aggregate)类型 TestContext, 为你的自定义上下文属性提供类型支持。
declare module 'vitest' {
  export interface TestContext {
    foo?: string
  }
}如果只想为特定的 beforeEach、afterEach、it 或 test hooks 提供属性类型,则可以将类型作为泛型传递。
interface LocalTestContext {
  foo: string
}
beforeEach<LocalTestContext>(async (context) => {
  // typeof context is 'TestContext & LocalTestContext'
  context.foo = 'bar'
})
it<LocalTestContext>('should work', ({ foo }) => {
  // typeof foo is 'string'
  console.log(foo) // 'bar'
})扩展断言(Matchers) 
由于 Vitest 兼容 Chai 和 Jest,所以可以根据个人喜好使用 chai.use API 或者 expect.extend。
- 可以使用对象包裹断言的形式调用 expect.extend方法扩展默认的断言。
expect.extend({
  // 第一个参数是接收值,其余参数将直接传给断言
  toBeFoo(received, expected) {
    const { isNot } = this
    return {
      // 请勿根据 isNot 参数更改你的 "pass" 值,Vitest 为你做了这件事情
      pass: received === 'foo',
      message: () => `${received} is${isNot ? ' not' : ''} foo`,
    }
  },
})断言方法可以访问上下文 this 对象中的这些属性:
- isNot
 如果断言是在- not方法上调用的(- expect(received).not.toBeFoo()),则返回- true。
- promise
 如果断言是在- resolved/rejected中调用的,它的值将包含此断言的名称。否则,它将是一个空字符串。
- equals
 这是一个工具函数,他可以帮助你比较两个值。如果是相同的则返回- true,反之返回- false。这个方法几乎在每个断言内部都有使用。默认情况下,它支持非对称的断言。
- utils
 它包含了一系列工具函数,可以使用它们来显示信息。
this 上下文也包含了当前测试的信息,可以通过调用 expect.getState() 来获取它,有用的属性是:
- currentTestName
 当前测试的全称(包括 describe 块)。
- testPath
 当前测试的路径。
断言的返回值应该兼容如下接口:
interface MatcherResult {
  pass: boolean
  message: () => string
  // 如果你传了这些参数,它们将自动出现在 diff 信息中,
  // 所以即便断言不通过,你也不必自己输出 diff
  actual?: unknown
  expected?: unknown
}- 使用 TypeScript 时,可以使用以下代码扩展默认的 Matchers 接口:
interface CustomMatchers<R = unknown> {
  toBeFoo(): R
}
declare global {
  namespace Vi {
    interface Assertion extends CustomMatchers {}
    interface AsymmetricMatchersContaining extends CustomMatchers {}
  }
  // jest.Matchers interface will also work.
}源码内联测试 
Vitest 还提供了一种方式,可以运行与你的代码实现放在一起的测试,允许测试与实现共享相同的闭包,并且能够在不导出的情况下针对私有状态进行测试。可用于:
- 小范围的功能或工具的单元测试
- 原型设计
- 内联断言 对于更复杂的测试,比如组件测试或 E2E 测试,建议使用单独的测试文件取而代之。
- 首先,在 if (i代码块内写一些测试代码并放在文件的末尾,例如:mport.meta.vitest) 
// src/index.ts
// 函数实现
export function add(...args: number[]) {
  return args.reduce((a, b) => a + b, 0)
}
// 源码内的测试套件
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)
  })
}- 更新 Vitest 配置文件内的 includeSource以获取到src/下的文件:
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    includeSource: ['src/**/*.{js,ts}'],
  },
})- 执行测试。
npx vitest- 对于生产环境的构建,你需要设置配置文件内的 define选项,让打包器清除无用的代码。
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
+ define: {
+   'import.meta.vitest ': 'undefined',
+ },
  test: {
    includeSource: ['src/**/*.{js,ts}']
  },
})- 要获得对 i的 TypeScript 支持,添加mport.meta.vitest vitest/importMeta到tsconfig.json:
// tsconfig.json
{
  "compilerOptions": {
    "types": [
+     "vitest/importMeta"
    ]
  }
}快照 
当希望确保函数的输出不会意外更改时,可以使用快照测试,兼容 Jest 快照测试。使用快照时,Vitest 将获取给定值的快照,将其比较时将参考存储在测试旁边的快照文件。如果两个快照不匹配,则测试将失败:要么更改是意外的,要么参考快照需要更新到测试结果的新版本。
要将一个值快照,你可以使用 expect() 的 toMatchSnapshot() API:
import { expect, it } from 'vitest'
it('renders correctly', () => {
  const result = toUpperCase('foobar')
  expect(result).toMatchSnapshot()
})此测试在第一次运行时,Vitest 会创建一个快照文件,如下所示:
// Vitest Snapshot v1
exports['toUpperCase 1'] = '"FOOBAR"'快照文件应该与代码更改一起提交,并作为代码审查过程的一部分进行审查。在随后的测试运行中,Vitest 会将执行的输出与之前的快照进行比较。如果他们匹配,测试就会通过。如果它们不匹配,要么测试运行时在你的代码中发现了应该修复的错误,要么实现已经更改,需要更新快照:
- 在监听(watch)模式下, 你可以在终端中键入 u键直接更新失败的快照。
- 或者,你可以在 CLI 中使用 --update或-u标记,vitest -u使 Vitest 进入快照更新模式。
指定超时阈值 
你可以选择将超时阈值(以毫秒为单位)作为第三个参数传递给测试。默认值为 5 秒。
import { test } from 'vitest'
test('name', async () => { /* ... */ }, 1000)Hooks 也可以接收超时阈值,默认值为 5 秒。
import { beforeAll } from 'vitest'
beforeAll(async () => { /* ... */ }, 1000)选择、跳过、待办测试套件和测试 
- 使用 .only仅运行某些测试套件或测试。
import { assert, describe, it } from 'vitest'
// 仅运行此测试套件(以及标记为 Only 的其他测试套件)
describe.only('suite', () => {
  it('test', () => {
    assert.equal(Math.sqrt(4), 3)
  })
})
describe('another suite', () => {
  it('skipped test', () => {
    // 已跳过测试,因为测试在 Only 模式下运行
    assert.equal(Math.sqrt(4), 3)
  })
  it.only('test', () => {
    // 仅运行此测试(以及标记为 Only 的其他测试)
    assert.equal(Math.sqrt(4), 2)
  })
})- 使用 .skip以避免运行某些测试套件或测试。
import { assert, describe, it } from 'vitest'
describe.skip('skipped suite', () => {
  it('test', () => {
    // 已跳过此测试套件,无错误
    assert.equal(Math.sqrt(4), 3)
  })
})
describe('suite', () => {
  it.skip('skipped test', () => {
    // 已跳过此测试,无错误
    assert.equal(Math.sqrt(4), 3)
  })
})- 使用 .todo留存将要实施的测试套件和测试的待办事项。
import { describe, it } from 'vitest'
// 此测试套件的报告中将显示一个条目
describe.todo('unimplemented suite')
// 此测试的报告中将显示一个条目
describe('suite', () => {
  it.todo('unimplemented test')
})类型测试 
- 从 Vitest 0.25.0 开始,Vitest 附带 expect-type 包,可以使用 expectTypeOf或assertType语法为你的类型编写测试。
- 在测试文件中触发的任何类型错误都将被视为测试错误,因此可以使用任何类型技巧来测试项目中的类型。
- 默认情况下,*.test-d.ts文件中的所有测试都被视为类型测试,但可以使用typecheck.include配置选项更改它。
- Vitest 不运行或编译这些文件,它们仅由编译器静态分析,因此你不能使用任何动态语句,所以不能使用动态测试名称和 test.each、test.runIf、test.skipIf、test.each、test.concurrentAPI,但可以使用其他 API,例如test、describe、.only、.skip和.todo。
- 使用 CLI 标志,如 --allowOnly和-t也支持类型检查。
import { assertType, expectTypeOf } from 'vitest'
import { mount } from './mount.js'
test('my types work properly', () => {
  expectTypeOf(mount).toBeFunction()
  expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>()
  // @ts-expect-error name is a string
  assertType(mount({ name: 42 }))
})- 在 package.json文件scripts部分添加如下命令:
{
  "scripts": {
    "typecheck": "vitest typecheck"
  }
}Vitest 使用 tsc --noEmit 或 vue-tsc --noEmit,具体取决于配置。
同时运行多个测试 
在连续测试中使用 .concurrent 将会并发运行它们。
import { describe, it } from 'vitest'
// 标记为concurrent的两个测试将并行运行
describe('suite', () => {
  it('serial test', async () => {
    /* ... */
  })
  it.concurrent('concurrent test 1', async ({ expect }) => {
    /* ... */
  })
  it.concurrent('concurrent test 2', async ({ expect }) => {
    /* ... */
  })
})在测试套件中使用 .concurrent,则其中的每个测试用例都将并发运行。
import { describe, it } from 'vitest'
// 此套件中的所有测试都将并行运行
describe.concurrent('suite', () => {
  it('concurrent test 1', async ({ expect }) => {
    /* ... */
  })
  it('concurrent test 2', async ({ expect }) => {
    /* ... */
  })
  it.concurrent('concurrent test 3', async ({ expect }) => {
    /* ... */
  })
})还可以将 .skip、.only 和 .todo 用于并发测试套件和测试用例。
Vitest 配置 
Vitest 的主要优势之一是它与 Vite 的统一配置,vitest 将读取你的根目录 vite.config.ts 以匹配插件,例如 resolve.alias 和 plugins 的配置将会在 Vitest 中开箱即用。
- 创建 vitest.config.ts,优先级将会最高。
- 将 --config选项传递给 CLI,例如vitest --config ./path/to/vitest.config.ts。
- 在 defineConfig上使用p或rocess.env.VITEST mode属性(如果没有被覆盖,将设置为test)有条件地在vite.config.ts中应用不同的配置。
使用 vite 的 defineConfig 还需要将 三斜线指令 写在配置文件的顶部,可以参考下面的格式:
/// <reference types="vitest" />
import { defineConfig } from 'vite'
export default defineConfig({
  test: {
    /* 使用global避免全局导入(description、test、expect) */
    // globals: true,
  },
})使用 vitest 的 defineConfig 可以参考下面的格式:
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    // ...
  },
})如果有需要,可以获取到 Vitest 的默认选项以扩展它们:
import { configDefaults, defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    exclude: [...configDefaults.exclude, 'packages/template/*'],
  },
})对象模拟(Mocking) 
在编写测试时,可能会因为时间问题,需要创建内部或外部服务的 “假” 版本,这通常被称为 对象模拟 操作。Vitest 通过 vi 提供了一些实用的函数用于解决这个问题。你可以使用 import { vi } from 'vitest' 或者 全局配置 进行访问它(当启用 全局配置 时)。
import { expect, vi } from 'vitest'
const fn = vi.fn()
fn('hello', 1)
expect(vi.isMockFunction(fn)).toBe(true)
expect(fn.mock.calls[0]).toEqual(['hello', 1])
fn.mockImplementation(arg => arg)
fn('world', 2)
expect(fn.mock.results[1].value).toBe('world')Vitest 支持 happy-dom 或 jsdom 来模拟 DOM 和浏览器 API。Vitest 并不内置它们,所以需要安装:
npm i -D happy-dom
# or
npm i -D jsdom然后,更改 environment 配置文件中的选项:
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    environment: 'happy-dom', // or 'jsdom', 'node'
  },
})测试覆盖率 
- Vitest 通过 c8 支持本机代码覆盖率。同时也支持 istanbul。默认情况下,启用 c8。可以通过将 test.coverage.provider设置为c8或istanbul来选择覆盖工具:
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    coverage: {
      provider: 'istanbul', // or 'c8'
    },
  },
})- 当你启动 Vitest 进程时,它会提示你自动安装相应的支持包:
npm i -D @vitest/coverage-c8
# or
npm i -D @vitest/coverage-istanbul- 要在启用的情况下进行测试,在 CLI 中传递 --coverage标志:
{
  "scripts": {
    "test": "vitest",
    "coverage": "vitest run --coverage"
  }
}- 要对其进行配置,需要在配置文件中设置 test.coverage选项:
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    coverage: {
      reporter: ['text', 'json', 'html'],
    },
  },
})- 也可以通过将 'custom'传递给test.coverage.provider来配置你的自定义覆盖率提供者:
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    coverage: {
      provider: 'custom',
      customProviderModule: 'my-custom-coverage-provider',
    },
  },
})自定义覆盖率提供者需要一个 customProviderModule 选项,它是一个模块名称或从中加载 CoverageProviderModule 的路径。它必须将实现 CoverageProviderModule 的对象导出为默认导出:
// my-custom-coverage-provider.ts
import type {
  CoverageProvider,
  CoverageProviderModule,
  ResolvedCoverageOptions,
  Vitest,
} from 'vitest'
const CustomCoverageProviderModule: CoverageProviderModule = {
  getProvider(): CoverageProvider {
    return new CustomCoverageProvider()
  },
  // Implements rest of the CoverageProviderModule ...
}
class CustomCoverageProvider implements CoverageProvider {
  name = 'custom-coverage-provider'
  options!: ResolvedCoverageOptions
  initialize(ctx: Vitest) {
    this.options = ctx.config.coverage
  }
  // Implements rest of the CoverageProvider ...
}
export default CustomCoverageProviderModule- 运行覆盖率报告时,会在项目的根目录中创建一个 coverage文件夹。如果想将它移动到不同的目录,使用vite.config.js文件中的test.coverage.reportsDirectory属性:
import { defineConfig } from 'vite'
export default defineConfig({
  test: {
    coverage: {
      reportsDirectory: './tests/unit/coverage',
    },
  },
})命令行 
在安装了 Vitest 的项目中,可以在 npm 脚本中使用 vitest 脚本,或者直接使用 npx vitest 运行它。以下是脚手架 Vitest 项目中的默认 npm 脚本:
{
  "scripts": {
    "test": "vitest",
    "coverage": "vitest run --coverage"
  }
}- vitest 在开发环境下默认启动时使用 监听模式(watch mode),当你修改源代码或测试文件时,Vitest 智能搜索模块依赖树并只重新运行相关测试,就像 HMR 在 Vite 的工作方式一样!
- 在 CI 环境(当 p出现时)中以rocess.env.CI 运行模式(run mode)启动,在不监视文更改的情况下执行单次运行。
- 可以使用 vitest watch或vitest run明确指定所需的模式。
- 可以使用 CLI 按名称筛选测试文件,例如 vitest basic将只执行包含basic路径名的测试文件。
basic.test.ts
basic-foo.test.ts- Vitest 默认启动多线程,可以通过 CLI 中的 --no-threads禁用。
- Vitest 还隔离了每个测试文件的运行环境,因此一个文件中的运行环境改变不会影响其他文件,可以通过将 --no-isolate传递给 CLI 来禁用隔离(以正确性换取运行性能)。
- 还可以指定其他 CLI 选项,例如 --port或--https,在项目中运行npx vitest --help获取有关 CLI 选项的完整列表。
Vitest UI 
Vitest 由 Vite 提供能力,在运行测试时有一个开发服务器。这允许 Vitest 提供一个漂亮的 UI 界面来查看并与测试交互。
- 安装:
npm i -D @vitest/ui- 通过传入 --ui参数来启动测试的 UI 界面:
vitest --ui- 通过 http://localhost:51204/__vitest__/可以访问 Vitest UI 界面。
- Vitest 0.26.0 开始, UI 也可以用作测试报告器。在 Vitest 配置中使用 'html'报告器生成 HTML 输出并预览测试结果,如果仍想在终端中实时查看测试的运行情况,不要忘记将default报告器添加到reporters选项。
// vitest.config.ts
export default {
  test: {
    reporters: ['default', 'html'],
  },
}- 要预览你的 HTML 报告,可以使用 vite preview命令:
npx vite preview --base __vitest__ --outDir ./html可以使用 --outputFile=<path> 配置选项配置输出,./html/index.html 是默认值。
API 
- describe 描述, 会形成一个作用域,用来组织测试和基准,使报告更加清晰 
- test 别名 it,定义了一组关于测试期望的方法,接收测试名称和一个含有测试期望的函数,可以提供一个超时时限(以毫秒为单位)用于指定等待多长时间后终止测试,默认为 5 秒,也可以通过 - testTimeout选项进行全局配置
- expect 用来创建断言 - not 将会否定断言
- toBe 可用于断言基础对象是否相等
- toBeDefined 断言检查值是否不等于 undefined
- toBeUndefined 断言检查值是否等于 undefined
- toBeNull 简单地断言检查值是否为 null,是 .toBe(null) 的别名
- toBeNaN 简单地断言是否为 NaN,是 .toBe(NaN) 的别名
- toBeTruthy 会将检查值转换为布尔值,断言该值是否为 true
- toBeFalsy 会将检测值转换为布尔值,断言该值是否为 false
- toBeTypeOf 断言检查值是否属于接收的类型
- toBeInstanceOf 断言检查值是否为接收的类的实例
- toBeGreaterThan 断言检查值是否大于接收值
- toBeGreaterThanOrEqual 断言检查值是否大于等于接收值
- toBeLessThan 断言检查值是否小于接收值
- toBeLessThanOrEqual 断言检查值是否小于等于接收值
- toEqual 断言检查值是否等于接收值,或者是同样的结构,如果是对象类型(将会使用递归的方法进行比较)
- toStrictEqual 断言检查值是否等于接收值或者同样的结构,如果是对象类型(将会使用递归的方法进行比较),并且会比较它们是否是相同的类型
- toContain 断言检查值是否在数组中,还可以检查一个字符串是否为另一个字符串的子串
- toContainEqual 断言在数组中是否包含具有特定结构和值的元素,就像对每个元素进行 toEqual 操作
- toHaveLength 断言一个对象是否具有 .length 属性,并且为数值
- toHaveProperty 用于断言对象上是否存在指定 key 的属性,同时该方法还提供了一个可选参数,用于进行深度对比,就像使用 toEqual 比较接收到的属性值
- toMatch 断言字符串是否匹配指定的正则表达式或字符串
- toMatchObject 用于断言对象是否匹配指定的对象属性的子集,还可以传递对象数组。如果我们只想检查两个数组的元素数量是否匹配,该方法就会很有用,它不同于 arrayContaining ,它允许接收数组中的额外元素
- toThrowError 断言函数在调用时是否抛出错误,可以提供一个可选参数来测试是否引发了指定的错误: - 正则表达式:错误信息通过正则表达式匹配
- 字符串:错误消息包含指定子串
 
- resolves 可以从待处理的 Promise 中去展开它的值,并使用通常的断言语句来断言它的值
- rejects 可以来展开 Promise 被拒绝的原因,并使用通常的断言语句来断言它的值
 
- expect.assertions 在测试通过或失败后,它将会验证在测试期间调用了多少次断言,常用于检查异步代码是否被调用了 
- expect.anything 这种非对称匹配器与相等检查一起使用时,将始终返回 true,如果你只是想确保该属性存在时很有用 
- expect.any 这种非对称匹配器与相等检查一起使用时,仅当 value 是指定构造函数的实例时才会返回 true,如果你有一个每次都生成的值,并且只想知道它以正确的类型存在是很有用 
- expect.arrayContaining 当与相等检查一起使用时,如果 value 是一个数组并包含指定的选项,则此非对称匹配器将返回 true,可以将 expect.not 与此匹配器一起使用来否定预期值 
- expect.objectContaining 当与相等检查一起使用时,如果 value 具有相似的结构,则此非对称匹配器将返回 true,可以将 expect.not 与此匹配器一起使用来否定预期值 
- expect.stringContaining 当与相等检查一起使用时,如果 value 是字符串并且包含指定的子字符串,则此非对称匹配器将返回 true,可以将 expect.not 与此匹配器一起使用来否定预期值 
- expect.stringMatching 当与相等检查一起使用时,如果 value 是字符串并且包含指定的子字符串或字符串匹配正则表达式,则此非对称匹配器将返回 true,可以将 expect.not 与此匹配器一起使用来否定预期值 
- not
import { expect, test } from 'vitest'
const input = Math.sqrt(16)
expect(input).not.to.equal(2) // chai API
expect(input).not.toBe(2) // jest API- toEqual和- toBe之间的区别
import { expect, test } from 'vitest'
const stockBill = {
  type: 'apples',
  count: 13,
}
const stockMary = {
  type: 'apples',
  count: 13,
}
test('stocks have the same properties', () => {
  expect(stockBill).toEqual(stockMary)
})
test('stocks are not the same', () => {
  expect(stockBill).not.toBe(stockMary)
})- toEqual和- toStrictEqual之间的区别
import { expect, test } from 'vitest'
class Stock {
  constructor(type) {
    this.type = type
  }
}
test('structurally the same, but semantically different', () => {
  expect(new Stock('apples')).toEqual({ type: 'apples' })
  expect(new Stock('apples')).not.toStrictEqual({ type: 'apples' })
})- toHaveProperty
import { expect, test } from 'vitest'
const invoice = {
  'isActive': true,
  'P.O': '12345',
  'customer': {
    first_name: 'John',
    last_name: 'Doe',
    location: 'China',
  },
  'total_amount': 5000,
  'items': [
    {
      type: 'apples',
      quantity: 10,
    },
    {
      type: 'oranges',
      quantity: 5,
    },
  ],
}
test('John Doe Invoice', () => {
  expect(invoice).toHaveProperty('isActive') // 断言 key 存在
  expect(invoice).toHaveProperty('total_amount', 5000) // 断言 key 存在且值相等
  expect(invoice).not.toHaveProperty('account') // 断言 key 不存在
  // 使用 dot 进行深度引用
  expect(invoice).toHaveProperty('customer.first_name')
  expect(invoice).toHaveProperty('customer.last_name', 'Doe')
  expect(invoice).not.toHaveProperty('customer.location', 'India')
  // 使用包含 key 的数组进行深度引用
  expect(invoice).toHaveProperty('items[0].type', 'apples')
  expect(invoice).toHaveProperty('items.0.type', 'apples') // 使用 dot 也可以工作
  // 在数组中包装你的 key 来避免它作为深度引用
  expect(invoice).toHaveProperty(['P.O'], '12345')
})- toMatch
import { expect, test } from 'vitest'
test('top fruits', () => {
  expect('top fruits include apple, orange and grape').toMatch(/apple/)
  expect('applefruits').toMatch('fruit') // toMatch 也可以是一个字符串
})- toMatchObject
import { expect, test } from 'vitest'
const johnInvoice = {
  isActive: true,
  customer: {
    first_name: 'John',
    last_name: 'Doe',
    location: 'China',
  },
  total_amount: 5000,
  items: [
    {
      type: 'apples',
      quantity: 10,
    },
    {
      type: 'oranges',
      quantity: 5,
    },
  ],
}
const johnDetails = {
  customer: {
    first_name: 'John',
    last_name: 'Doe',
    location: 'China',
  },
}
test('invoice has john personal details', () => {
  expect(johnInvoice).toMatchObject(johnDetails)
})
test('the number of elements must match exactly', () => {
  // 断言对象数组是否匹配
  expect([{ foo: 'bar' }, { baz: 1 }]).toMatchObject([
    { foo: 'bar' },
    { baz: 1 },
  ])
})- toThrowError
import { expect, test } from 'vitest'
function getFruitStock(type) {
  if (type === 'pineapples') {
    throw new DiabetesError(
      'Pineapples is not good for people with diabetes'
    )
  }
  // 可以做一些其他的事情
}
test('throws on pineapples', () => {
  // 测试错误消息是否在某处显示 "diabetes" :这些是等效的
  expect(() => getFruitStock('pineapples')).toThrowError(/diabetes/)
  expect(() => getFruitStock('pineapples')).toThrowError('diabetes')
  // 测试确切的错误信息
  expect(() => getFruitStock('pineapples')).toThrowError(
    /^Pineapples is not good for people with diabetes$/
  )
})- resolves
import { expect, test } from 'vitest'
async function buyApples() {
  return fetch('/buy/apples').then(r => r.json())
}
test('buyApples returns new stock id', async () => {
  // toEqual 现在返回一个 Promise ,所以我们必须等待它
  await expect(buyApples()).resolves.toEqual({ id: 1 }) // jest API
  await expect(buyApples()).resolves.to.equal({ id: 1 }) // chai API
})- rejects
import { expect, test } from 'vitest'
async function buyApples(id) {
  if (!id)
    throw new Error('no id')
}
test('buyApples throws an error when no id provided', async () => {
  // toThrow 现在返回一个 Promise ,所以你必须等待它
  await expect(buyApples()).rejects.toThrow('no id')
})- expect.assertions
import { expect, test } from 'vitest'
async function doAsync(...cbs) {
  await Promise.all(cbs.map((cb, index) => cb({ index })))
}
test('all assertions are called', async () => {
  expect.assertions(2)
  function callback1(data) {
    expect(data).toBeTruthy()
  }
  function callback2(data) {
    expect(data).toBeTruthy()
  }
  await doAsync(callback1, callback2)
})- expect.anything
import { expect, test } from 'vitest'
test('object has "apples" key', () => {
  expect({ apples: 22 }).toEqual({ apples: expect.anything() })
})- expect.any
import { expect, test } from 'vitest'
import { generateId } from './generators'
test('"id" is a number', () => {
  expect({ id: generateId() }).toEqual({ id: expect.any(Number) })
})- expect.arrayContaining
import { expect, test } from 'vitest'
test('basket includes fuji', () => {
  const basket = {
    varieties: ['Empire', 'Fuji', 'Gala'],
    count: 3,
  }
  expect(basket).toEqual({
    count: 3,
    varieties: expect.arrayContaining(['Fuji']),
  })
})- expect.objectContaining
import { expect, test } from 'vitest'
test('basket has empire apples', () => {
  const basket = {
    varieties: [
      {
        name: 'Empire',
        count: 1,
      },
    ],
  }
  expect(basket).toEqual({
    varieties: [expect.objectContaining({ name: 'Empire' })],
  })
})- expect.stringContaining
import { expect, test } from 'vitest'
test('variety has "Emp" in its name', () => {
  const variety = {
    name: 'Empire',
    count: 1,
  }
  expect(basket).toEqual({
    name: expect.stringContaining('Emp'),
    count: 1,
  })
})- expect.stringMatching
import { expect, test } from 'vitest'
test('variety ends with "re"', () => {
  const variety = {
    name: 'Empire',
    count: 1,
  }
  expect(basket).toEqual({
    name: expect.stringMatching(/re$/),
    count: 1,
  })
})使用 
- 在 package.json文件scripts部分添加如下命令:
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  }
}- 配置 vite.config.ts:
/// <reference types="vitest" />
// Configure Vitest (https://vitest.dev/config/)
import { defineConfig } from 'vite'
export default defineConfig({
  test: {
    /* for example, use global to avoid globals imports (describe, test, expect): */
    // globals: true,
  },
})- 定义文件。
- suite.test.ts
import { assert, describe, expect, it } from 'vitest'
describe('suite name', () => {
  it('foo', () => {
    assert.equal(Math.sqrt(4), 2)
  })
  it('bar', () => {
    expect(1 + 1).eq(2)
  })
  it('snapshot', () => {
    expect({ foo: 'bar' }).toMatchSnapshot()
  })
})- sum.ts
export default function sum(...numbers:number[]){
  return numbers.reduce((total,number)=>total+number,0)
}- sum.test.ts
import sum from './sum'
import {describe,expect,it} from "vitest"
describe("#sum", () => {
  it("returns 0 with no numbers", () => {
    expect(sum()).toBe(0)
  })
})- 运行测试 pnpm run test:run,在suite.test.ts文件目录下面生成了一个快照文件__snapshots__/suite.test.ts.snap:
// Vitest Snapshot v1
exports[`suite name > snapshot 1`] = `
{
  "foo": "bar",
}
`;组件测试示例 
- 定义组件:
- Case.tsx
import { defineComponent, ref, watchEffect } from 'vue'
export default defineComponent({
  name: 'TestComponent',
  props: {
    value: String,
  },
  emits: ['update:value'],
  setup(props, { emit }) {
    const local = ref('')
    watchEffect(() => {
      emit('update:value', local)
    })
    watchEffect(() => {
      local.value = props.value!
    })
    return {
      local,
    }
  },
  render() {
    return (
      <a-select v-model={[this.local, 'value']}>
        <a-select-option value="aaa">aaa</a-select-option>
      </a-select>
    )
  },
})- Link.tsx
import { defineComponent, PropType, ref } from "vue";
export type IType = 'default' | 'primary' | 'success' | 'warning' | 'danger'| 'info'
export type IColor = 'black' | 'blue' | 'green' | 'yellow'| 'red' | 'gray'
export const props = {
  type: {
    type: String as PropType<IType>,
    default: "default",
  },
  color: {
    type: String as PropType<IColor>,
    default: "black",
  },
  plain: {
    type: Boolean,
    default: true,
  },
  href: {
    type: String,
    required: true,
  },
} as const;
export default defineComponent({
  name: "CLink",
  props,
  setup(props, { slots }) {
        return () => (
      <a
      class={`
        text-${props.plain ? props.color + "-500" : "white"}
        hover:text-${props.color}-400
        cursor-pointer
        text-lg
        hover:text-white
        transition duration-300 ease-in-out transform hover:scale-105
        mx-1
        decoration-none
        `}
      href={props.href}      
    >
      {slots.default ? slots.default() : 'Link'}
    </a>
    );    
  },
});- vitest 本身是不支持单元组件测试的,需要安装 Vue Test Utils:pnpm add @vue/test-utils jsdom -D。
- 配置:
- package.json
{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  },
}- vite.config.ts
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import Jsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
  plugins: [Vue(), Jsx()],
  test: {
    globals: true,
    environment: 'jsdom',
    transformMode: {
      web: [/.[tj]sx$/],
    },
  },
})- 定义测试。
- case.test.ts
//创建一个包含被挂载和渲染的组件的Wrapper,和mount不同的是shallowMount仅限测试组件,不牵扯子组件内容
import { shallowMount } from '@vue/test-utils'
import { expect, test } from 'vitest'
import Case from '../src/Case'
test('mount component', () => {
  const wrapper = shallowMount(Case, {
    props: {
      value: 'test',
    },
    global: {
      stubs: ['a-select', 'a-select-option'],
    },
  })
  //返回 Wrapper DOM 节点的 HTML 字符串到快照
  expect(wrapper.html()).toMatchSnapshot()
})- link.test.ts
import Link from '../src/link/Link'
import { shallowMount } from '@vue/test-utils'
import { describe, expect, test } from 'vitest'
//使用shallowMount()方法挂载组件,并使用expect断言方法来检验组件的渲染是否正确
describe('Link', () => {
  test("mount @vue/test-utils", () => {
    const wrapper = shallowMount(Link, {
      slots: {
        default: 'Link'
      }
    });
    //断言
    expect(wrapper.text()).toBe("Link")
  })
})
//对组件颜色进行测试,测试默认link颜色
describe("Link", () => {
  test("default color is black", () => {
    // 使用 shallowMount 方法挂载组件
    const wrapper = shallowMount(Link);
    // 断言组件默认颜色是否是 black
    expect(wrapper.props().color).toBe("black");
  });
});- 运行测试 pnpm run test:run,在case.test.ts文件目录下面生成了一个快照文件__snapshots__/case.test.ts.snap:
// Vitest Snapshot v1
exports[`mount component 1`] = `"<a-select-stub value=\\"test\\"></a-select-stub>"`;