-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Clear and concise description of the problem
As a developer using Vitest I want to write setup/teardown logic that should just run for a single test file or describe block.
I would prefer to use beforeEach and afterEach hooks on top of fixtures, as they keep everything concise and readbale:
import { withDb as test } from "../fixtures
describe("My Integration Tests", () => {
beforeEach(()=>{
// ...
})
afterEach(()=>{
// ...
})
test(...)
})By default both hooks don't provide a type-safe context, so you can't just use fixtures in them like this:
beforeEach(({ db })=>{ // ERROR: db doesn't exist
db.put(...)
// ...
})Although db actually exists in the test context, it doesn't exist for TypeScript.
Suggested solution
It would be great if TestAPI's would expose all hooks in a type-safe manner
import { withDb as test } from "../fixtures
test.beforeEach(({ db })=>{ // should be typesafe now
// ...
})I actually wrote a helper to make this work so the implementation is very straightforward, as it's only about the types:
export type Fixtures<T extends TestAPI<any>> = T extends TestAPI<infer U> ? U : never
export type TypeSafeHooks<T extends TestAPI<any>> = {
beforeEach: typeof beforeEach<Fixtures<T>>
afterEach: typeof afterEach<Fixtures<T>>
beforeAll: typeof beforeAll
afterAll: typeof afterAll
}
export const attachTypeSafeHooks = <T extends TestAPI<any>>(testApi: T): T & TypeSafeHooks<T> => {
return Object.assign(testApi, {
beforeEach: beforeEach<Fixtures<T>>,
afterEach: afterEach<Fixtures<T>>,
beforeAll: beforeAll,
afterAll: afterAll,
})
}Usage:
import { withDb } from "../fixtures
const test = attachTypeSafeHooks(withDb)
test.beforeEach(({ db })=>{ // typesafe now
// ...
})It would be great if vitest would attach these hooks by default, so you can simply use test.beforeEach etc.
If you agree on the suggested solution, I'm glad to work on a PR.
Alternative
Alternative 1
The easiest way to make this work is by using global hooks and pass a generic:
import { withDb as test } from "../fixtures
type Fixtures<T extends TestAPI<any>> = T extends TestAPI<infer U> ? U : never
type WithDbFixtures = Fixtures<typeof withDb>
beforeEach<WithDbFixtures>(({ db })=>{ // works
// ...
})This is obviously not the biggest deal, but reduces the developer experience quite a bit for me. I want to make writing tests as pleasant as possible, as most of the folks don't like writing test anyway 😄
It's also prone to errors, if you pass the wrong generic type.
Alternative 2
The other alternative would be to add auto fixtures:
import { withDb as test } from "../fixtures
const test = withDb.extend({
forEach: [
async ({ db }, use) => {
console.log("beforeEach")
await use(undefined)
console.log("afterEach")
},
{ auto: true },
],
forAll: [
async ({ db }, use) => {
console.log("beforeAll")
await use(undefined)
console.log("afterAll")
},
{ auto: true, scope: "file" },
],
})This is even worse in terms of developer experience, as it bloats up test files quite fast and make them less readable, especially if you have multiple different setup/teardown logic in one file.
Additional context
As seen in this example, playwright also supports this behaviour. They go even a step further, by making beforeAll and afterAll hooks generic and type-safe by only including worker fixtures as context.
I actually worked a lot with playwright and was missing the feature in vitest, hence I opened this feature request.
It's also a bit confusing that the official test context docs say, that beforeEach and afterEach hooks are deprecated and won't work with test.extend, although they actually do. Would be great if this could also be clarified.
Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn't already an issue that request the same feature to avoid creating a duplicate.