vitest/docs/advanced/reporters.md

12 KiB

Extending Reporters

You can import reporters from vitest/reporters and extend them to create your custom reporters.

Extending Built-in Reporters

In general, you don't need to create your reporter from scratch. vitest comes with several default reporting programs that you can extend.

import { DefaultReporter } from 'vitest/reporters'

export default class MyDefaultReporter extends DefaultReporter {
  // do something
}

Of course, you can create your reporter from scratch. Just extend the BaseReporter class and implement the methods you need.

And here is an example of a custom reporter:

// ./custom-reporter.js
import { BaseReporter } from 'vitest/reporters'

export default class CustomReporter extends BaseReporter {
  onCollected() {
    const files = this.ctx.state.getFiles(this.watchFilters)
    this.reportTestSummary(files)
  }
}

Or implement the Reporter interface:

// ./custom-reporter.js
import { Reporter } from 'vitest/reporters'

export default class CustomReporter implements Reporter {
  onCollected() {
    // print something
  }
}

Then you can use your custom reporter in the vitest.config.ts file:

import { defineConfig } from 'vitest/config'
import CustomReporter from './custom-reporter.js'

export default defineConfig({
  test: {
    reporters: [new CustomReporter()],
  },
})

Reported Tasks

::: warning This is an experimental API. Breaking changes might not follow SemVer. Please pin Vitest's version when using it.

You can get access to this API by calling vitest.state.getReportedEntity(runnerTask):

import type { Vitest } from 'vitest/node'
import type { RunnerTestFile } from 'vitest'
import type { Reporter, TestModule } from 'vitest/reporters'

class MyReporter implements Reporter {
  ctx!: Vitest

  onInit(ctx: Vitest) {
    this.ctx = ctx
  }

  onFinished(files: RunnerTestFile[]) {
    for (const fileTask of files) {
      // note that the old task implementation uses "file" instead of "module"
      const testModule = this.ctx.state.getReportedEntity(fileTask) as TestModule
      for (const task of testModule.children) {
        //                          ^?
        console.log('finished', task.type, task.fullName)
      }
    }
  }
}

We are planning to stabilize this API in Vitest 2.1. :::

TestCase

TestCase represents a single test.

declare class TestCase {
  readonly type = 'test' | 'custom'
  /**
   * Task instance.
   * @experimental Public task API is experimental and does not follow semver.
   */
  readonly task: RunnerTestCase | RunnerCustomCase
  /**
   * The project associated with the test.
   */
  readonly project: TestProject
  /**
   * Direct reference to the test module where the test is defined.
   */
  readonly module: TestModule
  /**
   * Name of the test.
   */
  readonly name: string
  /**
   * Full name of the test including all parent suites separated with `>`.
   */
  readonly fullName: string
  /**
   * Unique identifier.
   * This ID is deterministic and will be the same for the same test across multiple runs.
   * The ID is based on the project name, module id and test position.
   */
  readonly id: string
  /**
   * Location in the module where the test was defined.
   * Locations are collected only if `includeTaskLocation` is enabled in the config.
   */
  readonly location: { line: number; column: number } | undefined
  /**
   * Parent suite. If the test was called directly inside the module, the parent will be the module itself.
   */
  readonly parent: TestSuite | TestModule
  /**
   * Options that test was initiated with.
   */
  readonly options: TaskOptions
  /**
   * Checks if the test did not fail the suite.
   * If the test is not finished yet or was skipped, it will return `true`.
   */
  ok(): boolean
  /**
   * Custom metadata that was attached to the test during its execution.
   */
  meta(): TaskMeta
  /**
   * Test results. Will be `undefined` if test is not finished yet or was just collected.
   */
  result(): TestResult | undefined
  /**
   * Useful information about the test like duration, memory usage, etc.
   */
  diagnostic(): TestDiagnostic | undefined
}

export type TestResult = TestResultPassed | TestResultFailed | TestResultSkipped

export interface TestResultPassed {
  /**
   * The test passed successfully.
   */
  state: 'passed'
  /**
   * Errors that were thrown during the test execution.
   *
   * **Note**: If test was retried successfully, errors will still be reported.
   */
  errors: TestError[] | undefined
}

export interface TestResultFailed {
  /**
   * The test failed to execute.
   */
  state: 'failed'
  /**
   * Errors that were thrown during the test execution.
   */
  errors: TestError[]
}

export interface TestResultSkipped {
  /**
   * The test was skipped with `only`, `skip` or `todo` flag.
   * You can see which one was used in the `mode` option.
   */
  state: 'skipped'
  /**
   * Skipped tests have no errors.
   */
  errors: undefined
}

export interface TestDiagnostic {
  /**
   * If the duration of the test is above `slowTestThreshold`.
   */
  slow: boolean
  /**
   * The amount of memory used by the test in bytes.
   * This value is only available if the test was executed with `logHeapUsage` flag.
   */
  heap: number | undefined
  /**
   * The time it takes to execute the test in ms.
   */
  duration: number
  /**
   * The time in ms when the test started.
   */
  startTime: number
  /**
   * The amount of times the test was retried.
   */
  retryCount: number
  /**
   * The amount of times the test was repeated as configured by `repeats` option.
   * This value can be lower if the test failed during the repeat and no `retry` is configured.
   */
  repeatCount: number
  /**
   * If test passed on a second retry.
   */
  flaky: boolean
}

TestSuite

TestSuite represents a single suite that contains tests and other suites.

declare class TestSuite {
  readonly type = 'suite'
  /**
   * Task instance.
   * @experimental Public task API is experimental and does not follow semver.
   */
  readonly task: RunnerTestSuite
  /**
   * The project associated with the test.
   */
  readonly project: TestProject
  /**
   * Direct reference to the test module where the suite is defined.
   */
  readonly module: TestModule
  /**
   * Name of the suite.
   */
  readonly name: string
  /**
   * Full name of the suite including all parent suites separated with `>`.
   */
  readonly fullName: string
  /**
   * Unique identifier.
   * This ID is deterministic and will be the same for the same test across multiple runs.
   * The ID is based on the project name, module id and test position.
   */
  readonly id: string
  /**
   * Location in the module where the suite was defined.
   * Locations are collected only if `includeTaskLocation` is enabled in the config.
   */
  readonly location: { line: number; column: number } | undefined
  /**
   * Collection of suites and tests that are part of this suite.
   */
  readonly children: TaskCollection
  /**
   * Options that the suite was initiated with.
   */
  readonly options: TaskOptions
}

TestModule

TestModule represents a single file that contains suites and tests.

declare class TestModule extends SuiteImplementation {
  readonly type = 'module'
  /**
   * Task instance.
   * @experimental Public task API is experimental and does not follow semver.
   */
  readonly task: RunnerTestFile
  /**
   * Collection of suites and tests that are part of this module.
   */
  readonly children: TestCollection
  /**
   * This is usually an absolute Unix file path.
   * It can be a virtual id if the file is not on the disk.
   * This value corresponds to Vite's `ModuleGraph` id.
   */
  readonly moduleId: string
  /**
   * Useful information about the module like duration, memory usage, etc.
   * If the module was not executed yet, all diagnostic values will return `0`.
   */
  diagnostic(): ModuleDiagnostic
}

export interface ModuleDiagnostic {
  /**
   * The time it takes to import and initiate an environment.
   */
  environmentSetupDuration: number
  /**
   * The time it takes Vitest to setup test harness (runner, mocks, etc.).
   */
  prepareDuration: number
  /**
   * The time it takes to import the test module.
   * This includes importing everything in the module and executing suite callbacks.
   */
  collectDuration: number
  /**
   * The time it takes to import the setup module.
   */
  setupDuration: number
  /**
   * Accumulated duration of all tests and hooks in the module.
   */
  duration: number
}

TestCollection

TestCollection represents a collection of suites and tests. It also provides useful methods to iterate over itself.

declare class TestCollection {
  /**
   * Returns the test or suite at a specific index in the array.
   */
  at(index: number): TestCase | TestSuite | undefined
  /**
   * The number of tests and suites in the collection.
   */
  size: number
  /**
   * Returns the collection in array form for easier manipulation.
   */
  array(): (TestCase | TestSuite)[]
  /**
   * Filters all suites that are part of this collection and its children.
   */
  allSuites(): IterableIterator<TestSuite>
  /**
   * Filters all tests that are part of this collection and its children.
   */
  allTests(state?: TestResult['state'] | 'running'): IterableIterator<TestCase>
  /**
   * Filters only the tests that are part of this collection.
   */
  tests(state?: TestResult['state'] | 'running'): IterableIterator<TestCase>
  /**
   * Filters only the suites that are part of this collection.
   */
  suites(): IterableIterator<TestSuite>;
  [Symbol.iterator](): IterableIterator<TestSuite | TestCase>
}

For example, you can iterate over all tests inside a module by calling testModule.children.allTests():

function onFileCollected(testModule: TestModule): void {
  console.log('collecting tests in', testModule.moduleId)

  // iterate over all tests and suites in the module
  for (const task of testModule.children.allTests()) {
    console.log('collected', task.type, task.fullName)
  }
}

TestProject

TestProject is a project assosiated with the module. Every test and suite inside that module will reference the same project.

Project is useful to get the configuration or provided context.

declare class TestProject {
  /**
   * The global vitest instance.
   * @experimental The public Vitest API is experimental and does not follow semver.
   */
  readonly vitest: Vitest
  /**
   * The workspace project this test project is associated with.
   * @experimental The public Vitest API is experimental and does not follow semver.
   */
  readonly workspaceProject: WorkspaceProject
  /**
   * Resolved project configuration.
   */
  readonly config: ResolvedProjectConfig
  /**
   * Resolved global configuration. If there are no workspace projects, this will be the same as `config`.
   */
  readonly globalConfig: ResolvedConfig
  /**
   * Serialized project configuration. This is the config that tests receive.
   */
  get serializedConfig(): SerializedConfig
  /**
   * The name of the project or an empty string if not set.
   */
  name(): string
  /**
   * Custom context provided to the project.
   */
  context(): ProvidedContext
  /**
   * Provide a custom serializable context to the project. This context will be available for tests once they run.
   */
  provide<T extends keyof ProvidedContext & string>(key: T, value: ProvidedContext[T]): void
}

Exported Reporters

vitest comes with a few built-in reporters that you can use out of the box.

Built-in reporters:

  1. BasicReporter
  2. DefaultReporter
  3. DotReporter
  4. JsonReporter
  5. VerboseReporter
  6. TapReporter
  7. JUnitReporter
  8. TapFlatReporter
  9. HangingProcessReporter

Base Abstract reporters:

  1. BaseReporter

Interface reporters:

  1. Reporter