Vitest async fixture destructuring
Vitest’s custom fixture support is great. Originally inspired by Playwright’s fixture setup, it’s now evolved into an auto-typed flexible builder:
const test = baseTest
.extend('client', () => new UserClient())
.extend('user', async ({ client }) => client.getUser())
test('Does the thing', async ({ user }) => {
// No need to await user, it's already resolved.
})TypeScript
I’ve long wondered how they manage to lazily initialise fixtures based on object destructuring — even with a Proxy or similar it’s not possible to hide the async part. Today I finally took a look, and it’s actually pretty cursed:
// https://github.com/vitest-dev/vitest/blob/v4.1/packages/runner/src/fixture.ts#L646
let fnString = filterOutComments(implementation.toString())TypeScript
The trick is to stringify the function and extract with regex the names of the fixtures requested. Vitest can then build up the fixture dependency tree and resolve these before passing them to your test fixture.
It’s also why this doesn’t work:
test('Does the thing', async (ctx) => {
// No proxy magic or anything wild, so this won't work -
// only the function arguments are parsed.
const { user } = ctx
})TypeScript
Turns out Playwright does the extact same thing.