Skip to content

Commit 0441f76

Browse files
fenghan34sheremet-va
andauthoredFeb 16, 2024··
feat(runner): support automatic fixtures (#5102)
Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
1 parent bc5b2d0 commit 0441f76

File tree

4 files changed

+99
-14
lines changed

4 files changed

+99
-14
lines changed
 

‎docs/guide/test-context.md

+26
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,32 @@ myTest('', ({ todos }) => {})
161161
When using `test.extend()` with fixtures, you should always use the object destructuring pattern `{ todos }` to access context both in fixture function and test function.
162162
:::
163163

164+
#### Automatic fixture
165+
166+
::: warning
167+
This feature is available since Vitest 1.3.0.
168+
:::
169+
170+
Vitest also supports the tuple syntax for fixtures, allowing you to pass options for each fixture. For example, you can use it to explicitly initialize a fixture, even if it's not being used in tests.
171+
172+
```ts
173+
import { test as base } from 'vitest'
174+
175+
const test = base.extend({
176+
fixture: [
177+
async ({}, use) => {
178+
// this function will run
179+
setup()
180+
await use()
181+
teardown()
182+
},
183+
{ auto: true } // Mark as an automatic fixture
184+
],
185+
})
186+
187+
test('', () => {})
188+
```
189+
164190
#### TypeScript
165191

166192
To provide fixture types for all your custom contexts, you can pass the fixtures type as a generic.

‎packages/runner/src/fixture.ts

+22-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { createDefer } from '@vitest/utils'
1+
import { createDefer, isObject } from '@vitest/utils'
22
import { getFixture } from './map'
3-
import type { TestContext } from './types'
3+
import type { FixtureOptions, TestContext } from './types'
44

5-
export interface FixtureItem {
5+
export interface FixtureItem extends FixtureOptions {
66
prop: string
77
value: any
8-
index: number
98
/**
109
* Indicates whether the fixture is a function
1110
*/
@@ -17,15 +16,24 @@ export interface FixtureItem {
1716
}
1817

1918
export function mergeContextFixtures(fixtures: Record<string, any>, context: { fixtures?: FixtureItem[] } = {}) {
19+
const fixtureOptionKeys = ['auto']
2020
const fixtureArray: FixtureItem[] = Object.entries(fixtures)
21-
.map(([prop, value], index) => {
22-
const isFn = typeof value === 'function'
23-
return {
24-
prop,
25-
value,
26-
index,
27-
isFn,
21+
.map(([prop, value]) => {
22+
const fixtureItem = { value } as FixtureItem
23+
24+
if (
25+
Array.isArray(value) && value.length >= 2
26+
&& isObject(value[1])
27+
&& Object.keys(value[1]).some(key => fixtureOptionKeys.includes(key))
28+
) {
29+
// fixture with options
30+
Object.assign(fixtureItem, value[1])
31+
fixtureItem.value = value[0]
2832
}
33+
34+
fixtureItem.prop = prop
35+
fixtureItem.isFn = typeof fixtureItem.value === 'function'
36+
return fixtureItem
2937
})
3038

3139
if (Array.isArray(context.fixtures))
@@ -67,7 +75,8 @@ export function withFixtures(fn: Function, testContext?: TestContext) {
6775
return fn(context)
6876

6977
const usedProps = getUsedProps(fn)
70-
if (!usedProps.length)
78+
const hasAutoFixture = fixtures.some(({ auto }) => auto)
79+
if (!usedProps.length && !hasAutoFixture)
7180
return fn(context)
7281

7382
if (!fixtureValueMaps.get(context))
@@ -78,7 +87,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) {
7887
cleanupFnArrayMap.set(context, [])
7988
const cleanupFnArray = cleanupFnArrayMap.get(context)!
8089

81-
const usedFixtures = fixtures.filter(({ prop }) => usedProps.includes(prop))
90+
const usedFixtures = fixtures.filter(({ prop, auto }) => auto || usedProps.includes(prop))
8291
const pendingFixtures = resolveDeps(usedFixtures)
8392

8493
if (!pendingFixtures.length)

‎packages/runner/src/types/tasks.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ export type TestAPI<ExtraContext = {}> = ChainableTestAPI<ExtraContext> & Extend
223223
K extends keyof ExtraContext ? ExtraContext[K] : never }>
224224
}
225225

226+
export interface FixtureOptions {
227+
/**
228+
* Whether to automatically set up current fixture, even though it's not being used in tests.
229+
*/
230+
auto?: boolean
231+
}
232+
226233
export type Use<T> = (value: T) => Promise<void>
227234
export type FixtureFn<T, K extends keyof T, ExtraContext> =
228235
(context: Omit<T, K> & ExtraContext, use: Use<T[K]>) => Promise<void>
@@ -231,7 +238,7 @@ export type Fixture<T, K extends keyof T, ExtraContext = {}> =
231238
? (T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never)
232239
: T[K] | (T[K] extends any ? FixtureFn<T, K, Omit<ExtraContext, Exclude<keyof T, K>>> : never)
233240
export type Fixtures<T extends Record<string, any>, ExtraContext = {}> = {
234-
[K in keyof T]: Fixture<T, K, ExtraContext & ExtendedContext<Test>>
241+
[K in keyof T]: Fixture<T, K, ExtraContext & ExtendedContext<Test>> | [Fixture<T, K, ExtraContext & ExtendedContext<Test>>, FixtureOptions?]
235242
}
236243

237244
export type InferFixturesTypes<T> = T extends TestAPI<infer C> ? C : T
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest'
2+
3+
const mockServer = { setup: vi.fn(), teardown: vi.fn() }
4+
const FnA = vi.fn()
5+
6+
const myTest = test.extend<{
7+
autoFixture: void
8+
normalFixture: any[]
9+
}>({
10+
autoFixture: [async ({}, use) => {
11+
await mockServer.setup()
12+
await use()
13+
await mockServer.teardown()
14+
}, { auto: true }],
15+
16+
normalFixture: [async () => {
17+
await FnA()
18+
}, {}],
19+
})
20+
21+
describe('fixture with options', () => {
22+
describe('automatic fixture', () => {
23+
beforeEach(() => {
24+
expect(mockServer.setup).toBeCalledTimes(1)
25+
})
26+
27+
afterAll(() => {
28+
expect(mockServer.setup).toBeCalledTimes(1)
29+
expect(mockServer.teardown).toBeCalledTimes(1)
30+
})
31+
32+
myTest('should setup mock server', () => {
33+
expect(mockServer.setup).toBeCalledTimes(1)
34+
})
35+
})
36+
37+
describe('normal fixture', () => {
38+
myTest('it is not a fixture with options', ({ normalFixture }) => {
39+
expect(FnA).not.toBeCalled()
40+
expect(normalFixture).toBeInstanceOf(Array)
41+
})
42+
})
43+
})

0 commit comments

Comments
 (0)
Please sign in to comment.