本文档适用于 v2 (旧版本)。如需查看最新版本请访问 https://cn.vitest.dev.

Skip to content

运行器 API

注意

这是高级 API。如果你只需要 运行测试,你可能不需要这个。它主要被库的作者使用。

你可以在你的配置文件中使用 runner 选项指定你的测试运行器的路径。这个文件应该有一个默认的导出,其中包含一个实现这些方法的类:

ts
export interface VitestRunner {
  /**
   * 这是在实际收集和运行测试之前被调用的第一件事情。
   */
  onBeforeCollect?: (paths: string[]) => unknown
  /**
   * 这是在收集测试后、"onBeforeRun" 之前被调用的。
   */
  onCollected?: (files: File[]) => unknown

  /**
   * 当测试运行程序应该取消下一次测试运行时调用。
   * 运行程序应该监听此方法,并在“onBeforeRunSuite”和“onBeforeRunTest”中将测试和套件标记为跳过。
   */
  onCancel?: (reason: CancelReason) => unknown

  /**
   * 在运行单个测试之前调用。此时还没有“result”。
   */
  onBeforeRunTask?: (test: TaskPopulated) => unknown
  /**
   * 这是在实际运行测试函数之前被调用的。
   * 此时已经有了带有 "state" 和 "startTime" 属性的 "result" 对象。
   */
  onBeforeTryTask?: (test: TaskPopulated, options: { retry: number, repeats: number }) => unknown
  /**
   * 这是在结果和状态都被设置之后被调用的。
   */
  onAfterRunTask?: (test: TaskPopulated) => unknown
  /**
   * 这是在运行测试函数后立即被调用的。此时还没有新的状态。
   * 如果测试函数抛出异常,将不会调用此方法。
   */
  onAfterTryTask?: (test: TaskPopulated, options: { retry: number, repeats: number }) => unknown

  /**
   * 这是在运行单个测试套件之前被调用的,此时还没有测试结果。
   */
  onBeforeRunSuite?: (suite: Suite) => unknown
  /**
   * 这是在运行单个测试套件之后被调用的,此时已经有了状态和测试结果。
   */
  onAfterRunSuite?: (suite: Suite) => unknown

  /**
   * 如果定义了这个方法,它将会替代 Vitest 常规的测试套件分割和处理方式。
   * 但 "before" 和 "after" 钩子函数仍然会被执行。
   */
  runSuite?: (suite: Suite) => Promise<void>
  /**
   * 如果定义了这个方法,它将会替代 Vitest 常规的测试处理方式。
   * 如果你有自定义的测试函数,这个方法就很有用。
   * 但 "before" 和 "after" 钩子函数仍然会被执行。
   */
  runTask?: (test: TaskPopulated) => Promise<void>

  /**
   * 当一个任务被更新时被调用。与报告器中的 "onTaskUpdate" 方法相同。
   * 但该方法在同一个线程中运行,与测试运行在同一个线程中。
   */
  onTaskUpdate?: (task: [string, TaskResult | undefined][]) => Promise<void>

  /**
   * 这是在运行收集的所有测试之前被调用的。
   */
  onBeforeRunFiles?: (files: File[]) => unknown
  /**
   * 这是在运行收集的所有测试后立即被调用的。
   */
  onAfterRunFiles?: (files: File[]) => unknown
  /**
   * 这个方法被用于 "test" 和 "custom" 处理程序。
   * 你可以在 "setupFiles" 中使用 "beforeAll" 来定义自定义上下文,而不是使用 runner。
   *
   * 更多信息请参考:https://vitest.dev/advanced/runner.html#your-task-function
   */
  extendTaskContext?: <T extends Test | Custom>(
    context: TaskContext<T>
  ) => TaskContext<T>
  /**
   * 当导入某些文件时被调用。在收集测试和导入设置文件时都可能会被调用。.
   */
  importFile: (filepath: string, source: VitestRunnerImportSource) => unknown
  /**
   * 公开可用的配置.
   */
  config: VitestRunnerConfig
}

当初始化这个类时,Vitest 会传递 Vitest 配置,你应该将它作为一个 config 属性暴露出来。

注意

Vitest 还会将 ViteNodeRunner 的实例作为 __vitest_executor 属性注入。你可以使用它来处理 importFile 方法中的文件(这是 TestRunnerBenchmarkRunner 的默认行为)。

ViteNodeRunner 暴露了 executeId 方法,用于在适用于 Vite 的环境中导入测试文件。这意味着它将在运行时解析导入并转换文件内容,以便 Node 能够理解它。

提示

快照支持和其他功能是依赖于测试运行器的。如果你想保留这些功能,可以从 vitest/runners 导入 VitestTestRunner 并将你的测试运行器继承该类。它还暴露了 BenchmarkNodeRunner,如果你想扩展基准测试功能的话也可以继承它。

Tasks

在 Vitest 内部,测试套件(Suite)和测试用例(Test)统一称为任务(tasks)。测试运行器会在收集所有测试前初始化一个 File 任务,该任务是 Suite 的超集并包含额外属性。每个任务(包括 File)都可通过 file 属性访问其所属文件信息。

ts
interface File extends Suite {
  /**
   * 所属线程池名称
   * @default 'forks'
   */
  pool?: string
  /**
   * UNIX 格式文件路径
   */
  filepath: string
  /**
   * 所属工作区项目名
   */
  projectName: string | undefined
  /**
   * 测试收集耗时
   * 耗时还包括导入所有文件依赖关系
   */
  collectDuration?: number
  /**
   * 导入 setup 文件耗时
   */
  setupDuration?: number
  /**
   * 仅初始化结构,不执行实际用例
   * 用于 Vitest 服务端状态预加载
   */
  local?: boolean
}

每个套件都有一个 tasks 属性,该属性在收集阶段填充。用于自上而下遍历任务树。

ts
interface Suite extends TaskBase {
  type: 'suite'
  /**
   * 文件任务。它是文件的根任务
   */
  file: File
  /**
   * 包含该测试套件中所有任务的数组
   */
  tasks: Task[]
}

每个任务都有 suite 属性,指向其所在的测试套件。若 testdescribe 在顶层初始化,则不会具有 suite属性(该属性 不等于 file!)。File 任务也永不具有 suite 属性。此特性可用于自底向上遍历任务树。

ts
interface Test<ExtraContext = object> extends TaskBase {
  type: 'test'
  /**
   * 将被传递给测试函数的测试上下文
   */
  context: TaskContext<Test> & ExtraContext & TestContext
  /**
   * 文件任务。它是该文件的根任务
   */
  file: File
  /**
   * 该任务是否通过调用 `t.skip()` 被跳过
   */
  pending?: boolean
  /**
   * 该任务是否应在失败时仍视为成功。如果任务失败,它将被标记为通过
   */
  fails?: boolean
  /**
   * 任务失败时将运行的钩子函数。执行顺序取决于 `sequence.hooks` 选项
   */
  onFailed?: OnTestFailedHandler[]
  /**
   * 任务完成后将运行的钩子函数。执行顺序取决于 `sequence.hooks` 选项
   */
  onFinished?: OnTestFinishedHandler[]
  /**
   * 存储来自异步断言的 promises,确保测试完成前等待这些异步操作
   */
  promises?: Promise<any>[]
}

你的任务函数

你可以通过扩展 Vitest 的任务系统来添加你自己的任务。一个任务是一个对象,是套件的一部分。它会自动通过 suite.task 方法添加到当前套件中:

custom.js
js
import { createTaskCollector, getCurrentSuite, setFn } from 'vitest/suite'

export { afterAll, beforeAll, describe } from 'vitest'

// 当 Vitest 收集任务时,将调用此函数
// createTaskCollector 只提供了所有的 "todo"/"each"/... 支持,你不必使用它
// 要支持自定义任务,你只需要调用 "getCurrentSuite().task()"
export const myCustomTask = createTaskCollector(function (name, fn, timeout) {
  getCurrentSuite().task(name, {
    ...this, // so "todo"/"skip" is tracked correctly
    meta: {
      customPropertyToDifferentiateTask: true,
    },
    handler: fn,
    timeout,
  })
})
tasks.test.js
js
import { afterAll, beforeAll, describe, myCustomTask } from './custom.js'
import { gardener } from './gardener.js'

describe('take care of the garden', () => {
  beforeAll(() => {
    gardener.putWorkingClothes()
  })

  myCustomTask('weed the grass', () => {
    gardener.weedTheGrass()
  })
  myCustomTask.todo('mow the lawn', () => {
    gardener.mowerTheLawn()
  })
  myCustomTask('water flowers', () => {
    gardener.waterFlowers()
  })

  afterAll(() => {
    gardener.goHome()
  })
})
bash
vitest ./garden/tasks.test.js

WARNING

如果你没有定义自定义运行器,也没有定义 runTest 方法,Vitest 将会尝试自动获取任务。如果你没有使用 setFn 添加一个函数,这个过程会失败。

Released under the MIT License.