Vitest 测试上下文完全指南:内置上下文、test.extend 夹具与源码级实现解析
2026/9/14 16:53:48 网站建设 项目流程

Vitest 测试上下文完全指南:内置上下文、test.extend 夹具与源码级实现解析

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

Vitest 的测试上下文(Test Context)借鉴自 Playwright Fixtures,为每个测试回调注入taskexpectskipsignalbench等内置能力,并允许通过test.extend定义可自动初始化、按作用域共享、自动清理的自定义夹具。读完本篇,你将掌握测试上下文的全部内置 API、Builder 模式与 Playwright 兼容对象语法两套扩展方式、test/file/worker三级作用域规则,并能对照 Vitest 仓库源码理解夹具解析、惰性初始化与清理执行的底层机制。

测试上下文:测试回调的第一个参数

Vitest 中每个测试回调的第一个参数就是测试上下文,它携带当前测试的元数据与一组可执行操作:

import { it } from 'vitest' it('should work', ({ task }) => { // prints name of the test console.log(task.name) })

在源码中,上下文的组装入口是 createTestContext:它把测试任务对象挂到context.task上,并填充skipannotateonTestFailedonTestFinished等方法。随后 TestRunner.extendTaskContext 以Object.defineProperty惰性注入expectbench——只有真正访问时才创建ExpectStatic实例(createExpect(context.task))与Bench实例,避免每个测试都付出构造开销。

内置测试上下文

以下字段由运行器内置提供,fixture.ts 中的TestFixtures._builtinFixtures常量完整列出了这份名单:tasksignalonTestFailedonTestFinishedskipannotatebench

task

一个只读对象,包含当前测试的元数据(名称、所属套件等),可直接用于日志输出或诊断:

it('should work', ({ task }) => { console.log(task.name) })

expect

绑定到当前测试的expectAPI:

import { it } from 'vitest' it('math is easy', ({ expect }) => { expect(2 + 2).toBe(4) })

这个 API 的价值在于并发快照测试:全局expect无法在并发执行时正确追踪每个测试的快照归属,而上下文的expect与当前任务绑定:

import { it } from 'vitest' it.concurrent('math is easy', ({ expect }) => { expect(2 + 2).toMatchInlineSnapshot() }) it.concurrent('math is hard', ({ expect }) => { expect(2 * 2).toMatchInlineSnapshot() })

skip

function skip(note?: string): never function skip(condition: boolean, note?: string): void

跳过后续测试执行并将测试标记为跳过:

import { expect, it } from 'vitest' it('math is hard', ({ skip }) => { skip() expect(2 + 2).toBe(5) })

自 Vitest 3.1 起,skip接受布尔参数实现条件跳过:

it('math is hard', ({ skip, mind }) => { skip(mind === 'foggy') expect(2 + 2).toBe(5) })

从 context.ts 的实现可以看到其机制:skip会把test.result置为skip状态并抛出PendingError,因此后续代码(包括未受保护的断言)不会真正执行;条件为false时则什么都不做、直接返回。

annotate(Vitest 3.2.0+)

function annotate( message: string, attachment?: TestAttachment, ): Promise<TestAnnotation> function annotate( message: string, type?: string, attachment?: TestAttachment, ): Promise<TestAnnotation>

为测试添加一条会在 reporter 中展示的测试注解:

test('annotations API', async ({ annotate }) => { await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues') })

实现上(context.ts),annotate会校验测试处于run状态、把注解记录为内部工件,再通过runner.onTestAnnotate解析后推入test.annotations,供 reporter 消费。

signal(Vitest 3.2.0+)

一个标准的AbortSignal,可由 Vitest 主动中止。触发中止的场景包括:

  • 测试超时(test times out)
  • 用户通过 Ctrl+C 手动取消测试运行
  • 程序化调用了vitest.cancelCurrentRun
  • 并行执行的另一个测试失败且设置了bail标志
it('stop request when test times out', async ({ signal }) => { await fetch('/resource', { signal }) }, 2000)

从源码看,每个上下文持有一个AbortController(context.ts 中的abortControllersWeakMap),超时经abortIfTimeout调用abortContextSignal触发中止,从而让fetch、WebSocket 等遵循AbortSignal的 API 自动停止。

bench(Vitest 5.0.0+)

bench夹具允许在普通测试中定义并运行基准测试,用于测量吞吐量、对比实现并断言相对性能:

import { expect, test } from 'vitest' test('compare parsers', async ({ bench }) => { const result = await bench.compare( bench('JSON.parse', () => { JSON.parse('{"key":"value"}') }), bench('custom parser', () => { customParse('{"key":"value"}') }), ) expect(result.get('JSON.parse')).toBeFasterThan(result.get('custom parser')) })

bench在 extendTaskContext 中是惰性 getter:首次访问才调用createBench(context.task, runnerConfig, moduleRunner)并缓存到benchInstances。完整文档参见 Benchmarks 指南。

onTestFailed/onTestFinished

onTestFailedonTestFinished是绑定到当前测试的钩子,适用于并发测试中只为某个特定测试做特殊处理的场景。实现上(context.ts),两个方法把回调包装进withTimeout(默认超时取hookTimeout配置)后分别推入test.onFailedtest.onFinished数组。

扩展测试上下文:test.extend

Vitest 允许通过test.extend把自定义工具、状态与夹具注入测试上下文。test.extend支持两种语法:Builder 模式(官方推荐,自动推导类型)与对象语法(Playwright 兼容,可一次性定义全部夹具)。

Builder 模式(Vitest 4.1.0+)

Builder 模式是推荐的夹具定义方式,它提供自动类型推导——TypeScript 直接从返回值推断每个夹具的类型,无需手工声明:

import { test as baseTest } from 'vitest' export const test = baseTest // Simple value - type is inferred as { port: number; host: string } .extend('config', { port: 3000, host: 'localhost' }) // Function fixture - type is inferred from return value .extend('server', async ({ config }) => { // TypeScript knows config is { port: number; host: string } return `http://${config.host}:${config.port}` })

在测试中使用:

import { expect } from 'vitest' import { test } from './my-test.js' test('server uses correct port', ({ config, server }) => { // TypeScript knows the types: // - config is { port: number; host: string } // - server is string expect(server).toBe('http://localhost:3000') expect(config.port).toBe(3000) })
使用onCleanup做 Setup 与清理

需要初始化/销毁逻辑的夹具应写成函数,用onCleanup回调注册在作用域结束后执行的清理逻辑:

import { test as baseTest } from 'vitest' export const test = baseTest .extend('tempFile', async ({}, { onCleanup }) => { const filePath = `/tmp/test-${Date.now()}.txt` await fs.writeFile(filePath, 'test data') // Register cleanup - runs after test completes onCleanup(async () => { await fs.unlink(filePath) }) return filePath })

更复杂的级联示例:

const test = baseTest .extend('database', { scope: 'file' }, async ({}, { onCleanup }) => { const db = await createDatabase() await db.connect() onCleanup(async () => { await db.disconnect() }) return db }) .extend('user', async ({ database }, { onCleanup }) => { const user = await database.createTestUser() onCleanup(async () => { await database.deleteUser(user.id) }) return user })

注意onCleanup每个夹具只能调用一次。若需要多个清理操作,要么合并进单个清理函数,要么把夹具拆分为多个更小的夹具。拆开是推荐做法,隔离性更好、依赖也更显式:

// ❌ This will throw an error const test = baseTest .extend('resources', async ({}, { onCleanup }) => { const a = await acquireA() onCleanup(() => releaseA(a)) const b = await acquireB() onCleanup(() => releaseB(b)) // Error: onCleanup can only be called once return { a, b } }) // ✅ Split into separate fixtures (recommended) const test = baseTest .extend('resourceA', async ({}, { onCleanup }) => { const a = await acquireA() onCleanup(() => releaseA(a)) return a }) .extend('resourceB', async ({}, { onCleanup }) => { const b = await acquireB() onCleanup(() => releaseB(b)) return b })
夹具选项

.extend()的第二个参数接受选项:

const test = baseTest // Automatic fixture - runs for every test even if not used .extend('metrics', { auto: true }, ({}, { onCleanup }) => { const metrics = new MetricsCollector() metrics.start() onCleanup(() => metrics.stop()) return metrics }) // Worker-scoped fixture - initialized once per worker .extend('config', { scope: 'worker' }, () => { return loadConfig() }) // File-scoped fixture - initialized once per file .extend('database', { scope: 'file' }, async ({ config }, { onCleanup }) => { const db = await createDatabase(config) onCleanup(() => db.close()) return db }) // Injected fixture - can be overridden via config .extend('baseUrl', { injected: true }, () => { return 'http://localhost:3000' })

测试作用域(默认)的夹具可以省略选项:

const test = baseTest .extend('simple', () => 'value')

从源码看,TestFixtures.parseUserFixtures 会把每个夹具归一化为{ auto, injected, scope }三个布尔/枚举字段,未指定选项时默认{ auto: false, injected: false, scope: 'test' };并对同名夹具的重复注册做校验——若scopeauto与已有注册不一致,会抛出FixtureDependencyError

访问其他夹具

每个夹具可通过第一个参数访问此前定义的夹具,函数式与非函数式夹具均适用:

const test = baseTest .extend('config', { apiUrl: 'https://api.example.com', port: 3000 }) .extend('client', ({ config }) => { // TypeScript knows config is { apiUrl: string; port: number } return new ApiClient(config.apiUrl) }) .extend('user', async ({ client }) => { // TypeScript knows client is ApiClient return await client.getCurrentUser() })

对象语法(Playwright 兼容)

如果你正从 Playwright 迁移,或偏好一次性定义所有夹具,Vitest 也支持 Playwright 兼容的对象语法:

import { test as baseTest } from 'vitest' export const test = baseTest.extend({ page: async ({}, use) => { // setup the fixture before each test function const page = await browser.newPage() // use the fixture value await use(page) // cleanup the fixture after each test function await page.close() }, baseUrl: 'http://localhost:3000' })

与 Builder 模式的关键区别在于清理方式——对象语法使用use()回调:

// Object syntax: cleanup code goes AFTER use() const test = baseTest.extend({ database: async ({}, use) => { const db = await createDatabase() await db.connect() await use(db) // Test runs here // Cleanup after the test await db.disconnect() } }) // Builder pattern: cleanup is registered with onCleanup() const test = baseTest .extend('database', async ({}, { onCleanup }) => { const db = await createDatabase() await db.connect() onCleanup(() => db.disconnect()) return db // Test runs after this returns })

类型提示:由于 TypeScript 无法从use()回调推断类型,使用对象语法时需要以泛型参数手工提供类型:

const test = baseTest.extend<{ page: Page baseUrl: string }>({ page: async ({}, use) => { const page = await browser.newPage() await use(page) await page.close() }, baseUrl: 'http://localhost:3000' })

源码印证:对象语法中的use()机制在 resolveFixtureFunction 中实现——它用一个useFnArgPromiseuse(arg)调用中提取夹具值,同时挂起测试执行直到清理阶段才恢复;如果夹具函数返回时从未调用use,会抛出Fixture "xxx" returned without calling "use"错误。这也解释了为什么use()必须在每条代码路径上调用。

选项的元组语法

对象语法下,用元组[fixtureFn, options]指定夹具选项:

const test = baseTest.extend({ // Auto fixture fixture: [ async ({}, use) => { setup() await use() teardown() }, { auto: true } ], // Scoped fixture database: [ async ({}, use) => { const db = await createDatabase() await use(db) await db.close() }, { scope: 'file' } ], // Injected fixture url: [ '/default', { injected: true } ], })

夹具的惰性初始化

Vitest 运行器会基于实际使用智能地初始化夹具并注入测试上下文:

import { test as baseTest } from 'vitest' const test = baseTest .extend('database', async () => { console.log('database initializing') return createDatabase() }) .extend('cache', async () => { return createCache() }) // database will not run test('no fixtures needed', () => {}) test('only cache', ({ cache }) => {}) // database will run test('needs database', ({ database }) => {})

注意:使用test.extend()定义夹具后,无论在夹具函数还是测试函数中,都应始终使用对象解构{ database }来访问上下文:

test('context must be destructured', (context) => { // [!code --] expect(context.database).toBeDefined() }) test('context must be destructured', ({ database }) => { // [!code ++] expect(database).toBeDefined() })

源码印证:惰性初始化依赖静态分析——getUsedProps 解析回调函数源码的第一个参数,提取解构出的属性名集合,因此强制要求对象解构写法(非解构参数会抛出FixtureParseError,rest 参数同样不支持);依赖闭包则通过 resolveDeps 做拓扑排序,遇到循环依赖时抛出Circular fixture dependency detected错误。对应测试用例见 test/e2e/fixtures/fails/test-extend/ 与 test/unit/test/fixture-initialization.test.ts。

扩展已扩展的测试

可以在已扩展的测试上继续添加夹具:

import { test as dbTest } from './my-test.js' export const test = dbTest .extend('user', ({ database }) => { return database.createUser() })

对象语法同样支持:

import { test as dbTest } from './my-test.js' export const test = dbTest.extend({ admin: async ({ database }, use) => { const admin = await database.createAdmin() await use(admin) await database.deleteUser(admin.id) } })

混用两种语法

Builder 模式可以链式接在对象扩展之后:

const test = baseTest // Object syntax for simple fixtures .extend<{ apiKey: string }>({ apiKey: 'test-key-123', }) // Builder pattern for complex fixtures with inference .extend('client', ({ apiKey }) => { // TypeScript knows apiKey is string return new ApiClient(apiKey) })

夹具作用域(Vitest 3.2.0+)

默认情况下夹具按测试初始化,可用scope选项在测试之间共享夹具。

注意:未显式指定作用域的夹具一律视为test作用域,这意味着它不能workerfile作用域夹具使用。若需跨作用域访问,请手动指定 scope:

test .extend('port', { scope: 'worker' }, 5000) .extend('db', { scope: 'worker' }, async ({ port }) => { return createDb(port) })

另外,非test作用域的夹具不能直接在describe块内覆盖(test.override('port', { scope: 'worker' }, 3000)会报错)——应在模块顶层覆盖,或使用injected选项在 project 配置 中提供值。还要注意在 non-isolate 模式下,覆盖worker夹具会影响其后所有在同一 worker 中运行的测试文件。

Test 作用域(默认)

Test 作用域夹具为每个测试创建全新实例:

const test = baseTest .extend('counter', () => { return { value: 0 } }) test('first test', ({ counter }) => { counter.value++ expect(counter.value).toBe(1) }) test('second test', ({ counter }) => { // Fresh instance, value is 0 again expect(counter.value).toBe(0) })

Test 作用域夹具可访问全部内置测试上下文(taskexpectskip等):

const test = baseTest .extend('testInfo', ({ task }) => { return { name: task.name } })
File 作用域

File 作用域夹具在每个测试文件内只初始化一次:

const test = baseTest .extend('database', { scope: 'file' }, async ({}, { onCleanup }) => { const db = await createDatabase() onCleanup(() => db.close()) return db }) test('first test', ({ database }) => { // Uses the same database instance }) test('second test', ({ database }) => { // Same database instance as first test })
Worker 作用域

Worker 作用域夹具在每个 worker 进程内只初始化一次:

const test = baseTest .extend('config', { scope: 'worker' }, () => { return await loadExpensiveConfig() })

默认每个文件运行在独立 worker 中,所以fileworker作用域效果相同;若关闭 isolation,worker 数量受maxWorkers限制,worker 作用域夹具会在同一 worker 中运行的多个文件间共享。在vmThreadsvmForks池中,由于每个文件拥有独立 VM 上下文,scope: 'worker'的等效行为退化为scope: 'file'——这一点在源码中体现得很直接:fixture.ts 在注册时发现runner.pool === 'vmThreads' || runner.pool === 'vmForks'会直接把item.scope改为'file'

作用域层级

夹具只能访问**同级或更高级(生命周期更长)**作用域的夹具:

夹具作用域可访问
worker仅其他 worker 夹具
fileworker + file 夹具
testworker + file + test 夹具 + 内置测试上下文
const test = baseTest .extend('config', { scope: 'worker' }, () => { return { apiUrl: 'https://api.example.com' } }) .extend('database', { scope: 'file' }, async ({ config }, { onCleanup }) => { // ✅ File fixture can access worker fixture const db = await createDatabase(config.apiUrl) onCleanup(() => db.close()) return db }) .extend('user', async ({ database, task }) => { // ✅ Test fixture can access file fixture AND test context return await database.createUser(task.name) })

提示:只有 test 作用域夹具能访问内置测试上下文(taskexpectskip等),因为 worker/file 夹具运行在任何具体测试之外。如果 file 作用域夹具需要文件路径,请改用expect.getState().testPath

该层级规则由 parseUserFixtures 的依赖校验 在注册期强制:当fixture.scope在作用序中的索引小于其依赖dep.scope时,抛出FixtureDependencyError("cannot depend on a xxx fixture")。相关端到端验证见 test/e2e/test/scoped-fixtures.test.ts。

类型安全的作用域访问(Vitest 3.2.0+)

Builder 模式下,TypeScript 自动强制执行基于作用域的访问规则——尝试从 file 作用域夹具访问 test 作用域夹具会在编译期报错。

如果你使用对象语法并希望获得同样的类型安全,可以通过$worker$file$test键显式声明各夹具归属的作用域:

const test = baseTest.extend<{ $worker: { config: Config } $file: { database: Database } $test: { user: User } }>({ config: [async ({}, use) => { await use(loadConfig()) }, { scope: 'worker' }], database: [async ({ config }, use) => { const db = await createDatabase(config) await use(db) await db.close() }, { scope: 'file' }], user: async ({ database }, use) => { const user = await database.createUser() await use(user) await database.deleteUser(user.id) }, })

这与 Builder 模式提供同等的编译期安全,把作用域违例从运行时提前到构建时捕获。

注入夹具(Injected)

自 Vitest 3 起,你可以为不同 project 提供不同的夹具值。启用方式为在选项中传{ injected: true }——若 project 配置 中未指定该键,则使用默认值:

import { test as baseTest } from 'vitest' const test = baseTest .extend('url', { injected: true }, '/default') test('works correctly', ({ url }) => { // url is "/default" in "project-new" // url is "/full" in "project-full" // url is "/empty" in "project-empty" })
import { defineConfig } from 'vitest/config' export default defineConfig({ test: { projects: [ { test: { name: 'project-new', }, }, { test: { name: 'project-full', provide: { url: '/full', }, }, }, { test: { name: 'project-empty', provide: { url: '/empty', }, }, }, ], }, })

实现上,元组形式的注入夹具在 parseUserFixtures 中优先取runner.injectValue?.(name)(由 TestRunner.injectValue 桥接到inject机制),取不到才回退到默认值。

覆盖夹具值(Vitest 4.1.0+)

可以使用test.override为特定套件(含其子级)覆盖夹具值,适用于不同测试场景需要不同夹具值的场合。

提示:覆盖时若未提供选项,Vitest 会自动继承原选项。但不能覆盖夹具的scopeauto选项。

Builder 模式(推荐)
import { test as baseTest, describe, expect } from 'vitest' const test = baseTest .extend('config', { port: 3000, host: 'localhost' }) .extend('server', ({ config }) => `http://${config.host}:${config.port}`) describe('production environment', () => { // Override with a new static value (chainable) test .override('config', { port: 8080, host: 'api.example.com' }) test('uses production config', ({ server }) => { expect(server).toBe('http://api.example.com:8080') }) }) describe('with custom server', () => { // Override with a function that can access other fixtures test.override('server', ({ config }) => { return `https://${config.host}:${config.port}/v2` }) test('uses custom server', ({ server }) => { expect(server).toBe('https://localhost:3000/v2') }) }) test('uses default values', ({ server }) => { expect(server).toBe('http://localhost:3000') })
链式多重覆盖

test.override返回测试 API 本身,可以连续链式调用:

describe('production environment', () => { test .override('environment', 'production') .override('port', 8080) .override('debug', false) test('uses production settings', ({ environment, port, debug }) => { expect(environment).toBe('production') expect(port).toBe(8080) expect(debug).toBe(false) }) })
对象语法覆盖

也可以一次性覆盖多个夹具:

describe('different configuration', () => { test.override({ config: { port: 4000, host: 'test.local' }, }) test('uses overwritten config', ({ config }) => { expect(config.port).toBe(4000) }) })
带清理的覆盖

以函数覆盖时同样可以使用onCleanup

describe('with custom database', () => { test.override('database', async ({ config }, { onCleanup }) => { const db = await createTestDatabase(config) onCleanup(() => db.drop()) return db }) test('uses custom database', ({ database }) => { // Uses the overwritten database }) })
嵌套作用域继承

覆盖会被嵌套套件继承,且可再次被覆盖:

describe('level 1', () => { test.override('value', 'one') test('uses level 1 value', ({ value }) => { expect(value).toBe('one') }) describe('level 2', () => { test.override('value', 'two') test('uses level 2 value', ({ value }) => { expect(value).toBe('two') }) }) test('still uses level 1 value', ({ value }) => { expect(value).toBe('one') }) })

注意test.override不能引入新夹具,新增夹具请用test.extend

test.scoped已弃用并被test.override取代,旧 API 仍可用但将在未来版本移除。

源码印证:覆盖的解析逻辑见 TestFixtures.override——它复制最近父级的注册表再解析新夹具,套件级覆盖存入this._overridesWeakMap 并按最近优先被 get 沿套件链查找,这正对应"嵌套继承、就近生效"的语义。

类型安全钩子

使用test.extend后,扩展得到的test对象提供感知扩展上下文的类型安全钩子:

const test = baseTest .extend('counter', { value: 0, increment() { this.value++ } }) // Unlike global hooks, these hooks are aware of the extended context test.beforeEach(({ counter }) => { counter.increment() }) test.afterEach(({ counter }) => { console.log('Final count:', counter.value) })
套件级钩子与夹具(Vitest 4.1.0+)

扩展后的test对象还提供可访问 file/worker 作用域夹具的beforeAllafterAllaroundAll钩子:

const test = baseTest .extend('config', { scope: 'file' }, () => loadConfig()) .extend('database', { scope: 'file' }, async ({ config }, { onCleanup }) => { const db = await createDatabase(config) onCleanup(() => db.close()) return db }) // Access file-scoped fixtures in suite-level hooks test.aroundAll(async (runSuite, { database }) => { await database.transaction(runSuite) }) test.beforeAll(async ({ database }) => { await database.createUsers() }) test.afterAll(async ({ database }) => { await database.removeUsers() })

重要:套件级钩子(beforeAllafterAllaroundAll必须调用在test.extend()返回的test对象上才能访问扩展夹具。使用全局beforeAll/afterAll/aroundAll函数是访问不到自定义夹具的:

import { test as baseTest, beforeAll } from 'vitest' const test = baseTest .extend('database', { scope: 'file' }, async ({}, { onCleanup }) => { const db = await createDatabase() onCleanup(() => db.close()) return db }) // ❌ WRONG: Global beforeAll doesn't have access to 'database' beforeAll(({ database }) => { // Error: 'database' is undefined }) // ✅ CORRECT: Use test.beforeAll to access fixtures test.beforeAll(({ database }) => { // 'database' is available })

此规则同样适用于beforeAllafterAllaroundAll全部套件级钩子。

提示:套件级钩子只能访问file 作用域与 worker 作用域夹具(含auto夹具);test 作用域夹具在这些钩子中不可用,强行访问会抛出错误。例如:

const test = baseTest .extend('testFixture', () => 'test-scoped') .extend('fileFixture', { scope: 'file' }, () => 'file-scoped') // ❌ Error: test-scoped fixtures not available in beforeAll test.beforeAll(({ testFixture }) => {}) // ✅ Works: file-scoped fixtures are available test.beforeAll(({ fileFixture }) => {})

源码印证:该限制由 withFixtures 在运行期强制——检测到套件钩子解析出scope === 'test'的夹具时抛出FixtureDependencyError,并提示改用file/worker作用域或迁移到beforeEachEach钩子。若全局钩子误用了夹具,validateSuiteHook 会抛出FixtureAccessError,明确提示"Did you forget to call it as test.beforeAll() instead of beforeAll()?"。相关测试覆盖见 test/unit/test/fixture-concurrent.test.ts、test/unit/test/test-extend.test.ts 与 test/unit/test/test-extend-with-top-level-hooks.test.ts。

小结

Vitest 测试上下文把"测试需要什么"从全局工具函数收拢到回调参数中:内置上下文(taskexpectskipannotatesignalbenchonTestFailed/onTestFinished)覆盖并发快照、优雅取消与注解上报等高频场景;test.extend则以 Playwright 式夹具模型提供自动初始化、依赖解析、onCleanup清理与三级作用域共享,配合injectedtest.override进一步支持多 project 差异化配置与套件级覆盖。结合 fixture.ts、context.ts 与 runners/test.ts 的源码,可以完整理解"按需初始化、作用域校验、惰性 expect/bench"背后的实现机制,并据此在自己的测试基建中设计可复用的夹具模块。

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询