跳至内容

测试 API 参考

以下类型用于下面的类型签名

ts
type Awaitable<T> = T | PromiseLike<T>
type TestFunction = () => Awaitable<void>

interface TestOptions {
  /**
   * Will fail the test if it takes too long to execute
   */
  timeout?: number
  /**
   * Will retry the test specific number of times if it fails
   *
   * @default 0
   */
  retry?: number
  /**
   * Will repeat the same test several times even if it fails each time
   * If you have "retry" option and it fails, it will use every retry in each cycle
   * Useful for debugging random failings
   *
   * @default 0
   */
  repeats?: number
}

Vitest 1.3.0 不再推荐使用选项作为最后一个参数。您将看到弃用消息,直到 2.0.0 版本,届时此语法将被删除。如果您需要传递选项,请使用 test 函数的第二个参数

ts
import { test } from 'vitest'

test('flaky test', () => {}, { retry: 3 }) 
test('flaky test', { retry: 3 }, () => {}) 

当测试函数返回一个 Promise 时,运行器将等待它被解析以收集异步期望。如果 Promise 被拒绝,测试将失败。

提示

在 Jest 中,TestFunction 也可以是 (done: DoneCallback) => void 类型。如果使用此形式,测试将不会在调用 done 之前结束。您可以使用 async 函数实现相同的效果,请参阅 迁移指南 Done Callback 部分

从 Vitest 1.3.0 开始,大多数选项都支持点语法和对象语法,您可以根据自己的喜好使用任何一种风格。

ts
import {  } from 'vitest'

.('skipped test', () => {
  // some logic that fails right now
})
ts
import {  } from 'vitest'

('skipped test', { : true }, () => {
  // some logic that fails right now
})

test

  • 别名: it

test 定义了一组相关的期望。它接收测试名称和一个包含要测试的期望的函数。

可选地,您可以提供一个超时时间(以毫秒为单位),用于指定在终止之前等待多长时间。默认值为 5 秒,可以通过 testTimeout 在全局范围内配置

ts
import { ,  } from 'vitest'

('should work as expected', () => {
  (.(4)).(2)
})

test.extend 0.32.3+

  • 别名: it.extend

使用 test.extend 用自定义夹具扩展测试上下文。这将返回一个新的 test,它也是可扩展的,因此您可以根据需要通过扩展它来组合更多夹具或覆盖现有夹具。有关更多信息,请参阅 扩展测试上下文

ts
import { expect, test } from 'vitest'

const todos = []
const archive = []

const myTest = test.extend({
  todos: async ({ task }, use) => {
    todos.push(1, 2, 3)
    await use(todos)
    todos.length = 0
  },
  archive
})

myTest('add item', ({ todos }) => {
  expect(todos.length).toBe(3)

  todos.push(4)
  expect(todos.length).toBe(4)
})

test.skip

  • 别名: it.skip

如果您想跳过运行某些测试,但出于任何原因不想删除代码,可以使用 test.skip 来避免运行它们。

ts
import { ,  } from 'vitest'

.('skipped test', () => {
  // Test skipped, no error
  .(.(4), 3)
})

您也可以通过在它的 上下文 上动态调用 skip 来跳过测试

ts
import { ,  } from 'vitest'

('skipped test', () => {
  .()
  // Test skipped, no error
  .(.(4), 3)
})

test.skipIf

  • 别名: it.skipIf

在某些情况下,您可能使用不同的环境多次运行测试,而某些测试可能是特定于环境的。与其用 if 包裹测试代码,不如使用 test.skipIf,当条件为真时跳过测试。

ts
import { ,  } from 'vitest'

const  = .. === 'development'

.()('prod only test', () => {
  // this test only runs in production
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

test.runIf

  • 别名: it.runIf

test.skipIf 相反。

ts
import { ,  } from 'vitest'

const  = .. === 'development'

.()('dev only test', () => {
  // this test only runs in development
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

test.only

  • 别名: it.only

使用 test.only 仅在给定套件中运行某些测试。这在调试时很有用。

可选地,您可以提供一个超时时间(以毫秒为单位),用于指定在终止之前等待多长时间。默认值为 5 秒,可以通过 testTimeout 在全局范围内配置。

ts
import { ,  } from 'vitest'

.('test', () => {
  // Only this test (and others marked with only) are run
  .(.(4), 2)
})

有时在特定文件中运行 only 测试非常有用,忽略整个测试套件中的所有其他测试,这些测试会污染输出。

为了做到这一点,请使用包含要测试的测试的特定文件运行 vitest

# vitest interesting.test.ts

test.concurrent

  • 别名: it.concurrent

test.concurrent 将连续测试标记为并行运行。它接收测试名称、一个包含要收集的测试的异步函数,以及一个可选的超时时间(以毫秒为单位)。

ts
import { ,  } from 'vitest'

// The two tests marked with concurrent will be run in parallel
('suite', () => {
  ('serial test', async () => { /* ... */ })
  .('concurrent test 1', async () => { /* ... */ })
  .('concurrent test 2', async () => { /* ... */ })
})

test.skiptest.onlytest.todo 可与并发测试一起使用。以下所有组合都是有效的

ts
test.concurrent(/* ... */)
test.skip.concurrent(/* ... */) // or test.concurrent.skip(/* ... */)
test.only.concurrent(/* ... */) // or test.concurrent.only(/* ... */)
test.todo.concurrent(/* ... */) // or test.concurrent.todo(/* ... */)

在运行并发测试时,快照和断言必须使用来自本地 测试上下文expect 来确保检测到正确的测试。

ts
test.concurrent('test 1', async ({ expect }) => {
  expect(foo).toMatchSnapshot()
})
test.concurrent('test 2', async ({ expect }) => {
  expect(foo).toMatchSnapshot()
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

test.sequential

  • 别名: it.sequential

test.sequential 将测试标记为顺序测试。如果您想在 describe.concurrent 中或使用 --sequence.concurrent 命令选项按顺序运行测试,这将很有用。

ts
// with config option { sequence: { concurrent: true } }
('concurrent test 1', async () => { /* ... */ })
('concurrent test 2', async () => { /* ... */ })

.('sequential test 1', async () => { /* ... */ })
.('sequential test 2', async () => { /* ... */ })

// within concurrent suite
.('suite', () => {
  ('concurrent test 1', async () => { /* ... */ })
  ('concurrent test 2', async () => { /* ... */ })

  .('sequential test 1', async () => { /* ... */ })
  .('sequential test 2', async () => { /* ... */ })
})

test.todo

  • 别名: it.todo

使用 test.todo 来存根以后要实现的测试。报告中将显示一个条目,用于指示您还需要实现多少个测试。

ts
// An entry will be shown in the report for this test
test.todo('unimplemented test')

test.fails

  • 别名: it.fails

使用 test.fails 来指示断言将显式失败。

ts
import { ,  } from 'vitest'

function () {
  return new ( => (1))
}
.('fail test', async () => {
  await (())..(1)
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

test.each

  • 别名: it.each

当您需要使用不同的变量运行相同的测试时,使用 test.each。您可以使用 printf 格式化 在测试名称中按测试函数参数的顺序注入参数。

  • %s:字符串
  • %d:数字
  • %i:整数
  • %f:浮点值
  • %j:json
  • %o:对象
  • %#:测试用例的索引
  • %%:单个百分号('%')
ts
.([
  [1, 1, 2],
  [1, 2, 3],
  [2, 1, 3],
])('add(%i, %i) -> %i', (, , ) => {
  ( + ).()
})

// this will return
// ✓ add(1, 1) -> 2
// ✓ add(1, 2) -> 3
// ✓ add(2, 1) -> 3

如果您使用对象作为参数,也可以使用 $ 前缀访问对象属性

ts
test.each([
  { a: 1, b: 1, expected: 2 },
  { a: 1, b: 2, expected: 3 },
  { a: 2, b: 1, expected: 3 },
])('add($a, $b) -> $expected', ({ a, b, expected }) => {
  expect(a + b).toBe(expected)
})

// this will return
// ✓ add(1, 1) -> 2
// ✓ add(1, 2) -> 3
// ✓ add(2, 1) -> 3

如果您使用对象作为参数,也可以使用 . 访问对象属性

ts
test.each`
a               | b      | expected
${{ val: 1 }}   | ${'b'} | ${'1b'}
${{ val: 2 }}   | ${'b'} | ${'2b'}
${{ val: 3 }}   | ${'b'} | ${'3b'}
`('add($a.val, $b) -> $expected', ({ a, b, expected }) => {
  expect(a.val + b).toBe(expected)
})

// this will return
// ✓ add(1, b) -> 1b
// ✓ add(2, b) -> 2b
// ✓ add(3, b) -> 3b

从 Vitest 0.25.3 开始,您也可以使用模板字符串表格。

  • 第一行应该是列名,用 | 分隔;
  • 使用 ${value} 语法作为模板字面量表达式提供的多个后续数据行。
ts
.`
  a               | b      | expected
  ${1}            | ${1}   | ${2}
  ${'a'}          | ${'b'} | ${'ab'}
  ${[]}           | ${'b'} | ${'b'}
  ${{}}           | ${'b'} | ${'[object Object]b'}
  ${{ : 1 }}   | ${'b'} | ${'[object Object]b'}
`('returns $expected when $a is added $b', ({ , ,  }) => {
  ( + ).()
})

如果您想访问 TestContext,请使用带有单个测试的 describe.each

提示

Vitest 使用 Chai format 方法处理 $values。如果值被截断太多,您可以在配置文件中增加 chaiConfig.truncateThreshold

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

bench

  • 类型: (name: string | Function, fn: BenchFunction, options?: BenchOptions) => void

bench 定义了一个基准测试。在 Vitest 术语中,基准测试是一个定义一系列操作的函数。Vitest 多次运行此函数以显示不同的性能结果。

Vitest 在幕后使用 tinybench 库,继承了所有可作为第三个参数使用的选项。

ts
import {  } from 'vitest'

('normal sorting', () => {
  const  = [1, 5, 4, 2, 3]
  .((, ) => {
    return  - 
  })
}, { : 1000 })
ts
export interface Options {
  /**
   * time needed for running a benchmark task (milliseconds)
   * @default 500
   */
  time?: number

  /**
   * number of times that a task should run if even the time option is finished
   * @default 10
   */
  iterations?: number

  /**
   * function to get the current timestamp in milliseconds
   */
  now?: () => number

  /**
   * An AbortSignal for aborting the benchmark
   */
  signal?: AbortSignal

  /**
   * warmup time (milliseconds)
   * @default 100ms
   */
  warmupTime?: number

  /**
   * warmup iterations
   * @default 5
   */
  warmupIterations?: number

  /**
   * setup function to run before each benchmark task (cycle)
   */
  setup?: Hook

  /**
   * teardown function to run after each benchmark task (cycle)
   */
  teardown?: Hook
}

bench.skip

  • 类型: (name: string | Function, fn: BenchFunction, options?: BenchOptions) => void

您可以使用 bench.skip 语法跳过运行某些基准测试。

ts
import {  } from 'vitest'

.('normal sorting', () => {
  const  = [1, 5, 4, 2, 3]
  .((, ) => {
    return  - 
  })
})

bench.only

  • 类型: (name: string | Function, fn: BenchFunction, options?: BenchOptions) => void

使用 bench.only 仅在给定套件中运行某些基准测试。这在调试时很有用。

ts
import {  } from 'vitest'

.('normal sorting', () => {
  const  = [1, 5, 4, 2, 3]
  .((, ) => {
    return  - 
  })
})

bench.todo

  • 类型: (name: string | Function) => void

使用 bench.todo 来存根以后要实现的基准测试。

ts
import {  } from 'vitest'

.('unimplemented test')

describe

当您在文件的顶层使用 testbench 时,它们将作为其隐式套件的一部分被收集。使用 describe,您可以在当前上下文中定义一个新的套件,作为一组相关的测试或基准测试以及其他嵌套套件。套件允许您组织测试和基准测试,以便报告更清晰。

ts
// basic.spec.ts
// organizing tests

import { , ,  } from 'vitest'

const  = {
  : true,
  : 32,
}

('person', () => {
  ('person is defined', () => {
    ().()
  })

  ('is active', () => {
    (.).()
  })

  ('age limit', () => {
    (.).(32)
  })
})
ts
// basic.bench.ts
// organizing benchmarks

import { ,  } from 'vitest'

('sort', () => {
  ('normal', () => {
    const  = [1, 5, 4, 2, 3]
    .((, ) => {
      return  - 
    })
  })

  ('reverse', () => {
    const  = [1, 5, 4, 2, 3]
    .().((, ) => {
      return  - 
    })
  })
})

如果您有测试或基准测试的层次结构,您也可以嵌套 describe 块

ts
import { , ,  } from 'vitest'

function (: number | string) {
  if (typeof  !== 'number')
    throw new ('Value must be a number')

  return .(2).().(/\B(?=(\d{3})+(?!\d))/g, ',')
}

('numberToCurrency', () => {
  ('given an invalid number', () => {
    ('composed of non-numbers to throw error', () => {
      (() => ('abc')).()
    })
  })

  ('given a valid number', () => {
    ('returns the correct currency format', () => {
      ((10000)).('10,000.00')
    })
  })
})

describe.skip

  • 别名: suite.skip

在套件中使用 describe.skip 来避免运行特定的 describe 块。

ts
import { , ,  } from 'vitest'

.('skipped suite', () => {
  ('sqrt', () => {
    // Suite skipped, no error
    .(.(4), 3)
  })
})

describe.skipIf

  • 别名: suite.skipIf

在某些情况下,您可能使用不同的环境多次运行套件,而某些套件可能是特定于环境的。与其用 if 包裹套件,不如使用 describe.skipIf,当条件为真时跳过套件。

ts
import { ,  } from 'vitest'

const  = .. === 'development'

.()('prod only test suite', () => {
  // this test suite only runs in production
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

describe.runIf

  • 别名: suite.runIf

describe.skipIf 相反。

ts
import { , ,  } from 'vitest'

const  = .. === 'development'

.()('dev only test suite', () => {
  // this test suite only runs in development
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

describe.only

  • 类型: (name: string | Function, fn: TestFunction, options?: number | TestOptions) => void

使用 describe.only 仅运行某些套件

ts
// Only this suite (and others marked with only) are run
.('suite', () => {
  ('sqrt', () => {
    .(.(4), 3)
  })
})

('other suite', () => {
  // ... will be skipped
})

有时在特定文件中运行 only 测试非常有用,忽略整个测试套件中的所有其他测试,这些测试会污染输出。

为了做到这一点,请使用包含要测试的测试的特定文件运行 vitest

# vitest interesting.test.ts

describe.concurrent

  • 别名: suite.concurrent

describe.concurrent 在套件中将每个测试标记为并发测试

ts
// All tests within this suite will be run in parallel
.('suite', () => {
  ('concurrent test 1', async () => { /* ... */ })
  ('concurrent test 2', async () => { /* ... */ })
  .('concurrent test 3', async () => { /* ... */ })
})

.skip.only.todo 可与并发套件一起使用。以下所有组合都是有效的

ts
describe.concurrent(/* ... */)
describe.skip.concurrent(/* ... */) // or describe.concurrent.skip(/* ... */)
describe.only.concurrent(/* ... */) // or describe.concurrent.only(/* ... */)
describe.todo.concurrent(/* ... */) // or describe.concurrent.todo(/* ... */)

在运行并发测试时,快照和断言必须使用来自本地 测试上下文expect 来确保检测到正确的测试。

ts
describe.concurrent('suite', () => {
  test('concurrent test 1', async ({ expect }) => {
    expect(foo).toMatchSnapshot()
  })
  test('concurrent test 2', async ({ expect }) => {
    expect(foo).toMatchSnapshot()
  })
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

describe.sequential

  • 别名: suite.sequential

describe.sequential 在套件中标记每个测试为顺序执行。如果您希望在 describe.concurrent 中或使用 --sequence.concurrent 命令选项在套件内按顺序运行测试,这将很有用。

ts
.('suite', () => {
  ('concurrent test 1', async () => { /* ... */ })
  ('concurrent test 2', async () => { /* ... */ })

  .('', () => {
    ('sequential test 1', async () => { /* ... */ })
    ('sequential test 2', async () => { /* ... */ })
  })
})

describe.shuffle

  • 别名: suite.shuffle

Vitest 提供了一种通过 CLI 标志 --sequence.shuffle 或配置选项 sequence.shuffle 以随机顺序运行所有测试的方法,但如果您希望仅让测试套件的一部分以随机顺序运行测试,可以使用此标志标记它。

ts
.('suite', () => {
  ('random test 1', async () => { /* ... */ })
  ('random test 2', async () => { /* ... */ })
  ('random test 3', async () => { /* ... */ })
})
// order depends on sequence.seed option in config (Date.now() by default)

.skip.only.todo 可与随机套件一起使用。

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

describe.todo

  • 别名: suite.todo

使用 describe.todo 来存根稍后要实现的套件。报告中将显示一个条目,以便您知道还需要实现多少测试。

ts
// An entry will be shown in the report for this suite
describe.todo('unimplemented suite')

describe.each

  • 别名: suite.each

如果您有多个测试依赖于相同的数据,请使用 describe.each

ts
.([
  { : 1, : 1, : 2 },
  { : 1, : 2, : 3 },
  { : 2, : 1, : 3 },
])('describe object add($a, $b)', ({ , ,  }) => {
  (`returns ${}`, () => {
    ( + ).()
  })

  (`returned value not be greater than ${}`, () => {
    ( + )..()
  })

  (`returned value not be less than ${}`, () => {
    ( + )..()
  })
})

从 Vitest 0.25.3 开始,您也可以使用模板字符串表格。

  • 第一行应该是列名,用 | 分隔;
  • 使用 ${value} 语法作为模板字面量表达式提供的多个后续数据行。
ts
.`
  a               | b      | expected
  ${1}            | ${1}   | ${2}
  ${'a'}          | ${'b'} | ${'ab'}
  ${[]}           | ${'b'} | ${'b'}
  ${{}}           | ${'b'} | ${'[object Object]b'}
  ${{ : 1 }}   | ${'b'} | ${'[object Object]b'}
`('describe template string add($a, $b)', ({ , ,  }) => {
  (`returns ${}`, () => {
    ( + ).()
  })
})

警告

当使用 Vitest 作为 类型检查器 时,您不能使用此语法。

设置和拆卸

这些函数允许您挂钩到测试的生命周期,以避免重复设置和拆卸代码。它们适用于当前上下文:如果它们在顶层使用,则为文件,如果它们在 describe 块内,则为当前套件。当您将 Vitest 作为类型检查器运行时,这些钩子不会被调用。

beforeEach

  • 类型: beforeEach(fn: () => Awaitable<void>, timeout?: number)

注册一个回调函数,该函数将在当前上下文中运行的每个测试之前被调用。如果函数返回一个 Promise,Vitest 将等待 Promise 解决后才运行测试。

可以选择传递一个超时时间(以毫秒为单位),定义在终止之前等待多长时间。默认值为 5 秒。

ts
import { beforeEach } from 'vitest'

beforeEach(async () => {
  // Clear mocks and add some testing data after before each test run
  await stopMocking()
  await addUser({ name: 'John' })
})

在这里,beforeEach 确保为每个测试添加用户。

从 Vitest v0.10.0 开始,beforeEach 还接受一个可选的清理函数(等效于 afterEach)。

ts
import { beforeEach } from 'vitest'

beforeEach(async () => {
  // called once before each test run
  await prepareSomething()

  // clean up function, called once after each test run
  return async () => {
    await resetSomething()
  }
})

afterEach

  • 类型: afterEach(fn: () => Awaitable<void>, timeout?: number)

注册一个回调函数,该函数将在当前上下文中完成的每个测试之后被调用。如果函数返回一个 Promise,Vitest 将等待 Promise 解决后才继续。

可以选择提供一个超时时间(以毫秒为单位),用于指定在终止之前等待多长时间。默认值为 5 秒。

ts
import { afterEach } from 'vitest'

afterEach(async () => {
  await clearTestingData() // clear testing data after each test run
})

在这里,afterEach 确保在每个测试运行后清除测试数据。

提示

Vitest 1.3.0 添加了 onTestFinished 钩子。您可以在测试执行期间调用它,以便在测试完成运行后清理任何状态。

beforeAll

  • 类型: beforeAll(fn: () => Awaitable<void>, timeout?: number)

注册一个回调函数,该函数将在开始运行当前上下文中的所有测试之前被调用一次。如果函数返回一个 Promise,Vitest 将等待 Promise 解决后才运行测试。

可以选择提供一个超时时间(以毫秒为单位),用于指定在终止之前等待多长时间。默认值为 5 秒。

ts
import { beforeAll } from 'vitest'

beforeAll(async () => {
  await startMocking() // called once before all tests run
})

在这里,beforeAll 确保在测试运行之前设置模拟数据。

从 Vitest v0.10.0 开始,beforeAll 还接受一个可选的清理函数(等效于 afterAll)。

ts
import { beforeAll } from 'vitest'

beforeAll(async () => {
  // called once before all tests run
  await startMocking()

  // clean up function, called once after all tests run
  return async () => {
    await stopMocking()
  }
})

afterAll

  • 类型: afterAll(fn: () => Awaitable<void>, timeout?: number)

注册一个回调函数,该函数将在当前上下文中的所有测试运行完毕后被调用一次。如果函数返回一个 Promise,Vitest 将等待 Promise 解决后才继续。

可以选择提供一个超时时间(以毫秒为单位),用于指定在终止之前等待多长时间。默认值为 5 秒。

ts
import { afterAll } from 'vitest'

afterAll(async () => {
  await stopMocking() // this method is called after all tests run
})

在这里,afterAll 确保在所有测试运行完毕后调用 stopMocking 方法。

测试钩子

Vitest 提供了一些钩子,您可以在测试执行期间调用它们,以便在测试完成运行后清理状态。

警告

如果在测试主体之外调用这些钩子,它们将抛出错误。

onTestFinished 1.3.0+

此钩子始终在测试完成运行后被调用。它在 afterEach 钩子之后被调用,因为它们可能会影响测试结果。它接收一个包含当前测试结果的 TaskResult 对象。

ts
import { onTestFinished, test } from 'vitest'

test('performs a query', () => {
  const db = connectDb()
  onTestFinished(() => db.close())
  db.query('SELECT * FROM users')
})

警告

如果您正在并发运行测试,您应该始终从测试上下文中使用 onTestFinished 钩子,因为 Vitest 不会在全局钩子中跟踪并发测试

ts
import { test } from 'vitest'

test.concurrent('performs a query', ({ onTestFinished }) => {
  const db = connectDb()
  onTestFinished(() => db.close())
  db.query('SELECT * FROM users')
})

此钩子在创建可重用逻辑时特别有用

ts
// this can be in a separate file
function getTestDb() {
  const db = connectMockedDb()
  onTestFinished(() => db.close())
  return db
}

test('performs a user query', async () => {
  const db = getTestDb()
  expect(
    await db.query('SELECT * from users').perform()
  ).toEqual([])
})

test('performs an organization query', async () => {
  const db = getTestDb()
  expect(
    await db.query('SELECT * from organizations').perform()
  ).toEqual([])
})

提示

此钩子始终以相反的顺序被调用,并且不受 sequence.hooks 选项的影响。

onTestFailed

此钩子仅在测试失败后被调用。它在 afterEach 钩子之后被调用,因为它们可能会影响测试结果。它接收一个包含当前测试结果的 TaskResult 对象。此钩子对于调试很有用。

ts
import { onTestFailed, test } from 'vitest'

test('performs a query', () => {
  const db = connectDb()
  onTestFailed((e) => {
    console.log(e.result.errors)
  })
  db.query('SELECT * FROM users')
})

警告

如果您正在并发运行测试,您应该始终从测试上下文中使用 onTestFailed 钩子,因为 Vitest 不会在全局钩子中跟踪并发测试

ts
import { test } from 'vitest'

test.concurrent('performs a query', ({ onTestFailed }) => {
  const db = connectDb()
  onTestFailed((result) => {
    console.log(result.errors)
  })
  db.query('SELECT * FROM users')
})