Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/runner/src/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { FixtureAccessError, FixtureDependencyError, FixtureParseError } from '.
import { getTestFixtures } from './map'
import { getCurrentSuite } from './suite'

const FIXTURE_STACK_TRACE_KEY = Symbol.for('VITEST_FIXTURE_STACK_TRACE')

export interface TestFixtureItem extends FixtureOptions {
name: string
value: unknown
Expand Down Expand Up @@ -187,6 +189,10 @@ export class TestFixtures {
parent,
}

if (isFixtureFunction(value)) {
Object.assign(value, { [FIXTURE_STACK_TRACE_KEY]: new Error('STACK_TRACE_ERROR') })
}

registrations.set(name, item)

if (item.scope === 'worker' && (runner.pool === 'vmThreads' || runner.pool === 'vmForks')) {
Expand Down Expand Up @@ -427,6 +433,7 @@ function resolveTestFixtureValue(

return resolveFixtureFunction(
fixture.value,
fixture.name,
context,
cleanupFnArray,
)
Expand Down Expand Up @@ -463,6 +470,7 @@ async function resolveScopeFixtureValue(

const promise = resolveFixtureFunction(
fixture.value,
fixture.name,
fixture.scope === 'file' ? { ...workerContext, ...fileContext } : fixtureContext,
cleanupFnFileArray,
).then((value) => {
Expand All @@ -479,11 +487,16 @@ async function resolveFixtureFunction(
context: unknown,
useFn: (arg: unknown) => Promise<void>,
) => Promise<void>,
fixtureName: string,
context: unknown,
cleanupFnArray: (() => void | Promise<void>)[],
): Promise<unknown> {
// wait for `use` call to extract fixture value
const useFnArgPromise = createDefer()
const stackTraceError
= FIXTURE_STACK_TRACE_KEY in fixtureFn && fixtureFn[FIXTURE_STACK_TRACE_KEY] instanceof Error
? fixtureFn[FIXTURE_STACK_TRACE_KEY]
: undefined
let isUseFnArgResolved = false

const fixtureReturn = fixtureFn(context, async (useFnArg: unknown) => {
Expand All @@ -500,6 +513,17 @@ async function resolveFixtureFunction(
await fixtureReturn
})
await useReturnPromise
}).then(() => {
// fixture returned without calling use()
if (!isUseFnArgResolved) {
const error = new Error(
`Fixture "${fixtureName}" returned without calling "use". Make sure to call "use" in every code path of the fixture function.`,
)
if (stackTraceError?.stack) {
error.stack = error.message + stackTraceError.stack.replace(stackTraceError.message, '')
}
useFnArgPromise.reject(error)
}
}).catch((e: unknown) => {
// treat fixture setup error as test failure
if (!isUseFnArgResolved) {
Expand Down
42 changes: 42 additions & 0 deletions test/cli/test/scoped-fixtures.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,48 @@ test('test fixture cannot import from file fixture', async () => {
`)
})

test('fixture returned without calling use', async () => {
const { stderr } = await runInlineTests({
'basic.test.ts': () => {
const extendedTest = it.extend<{
value: string | undefined
setup: void
}>({
value: undefined,
setup: [
async ({ value }, use) => {
if (!value) {
return
}
await use(undefined)
},
{ auto: true },
],
})

extendedTest('should fail with descriptive error', () => {})
},
}, { globals: true })
expect(stderr).toMatchInlineSnapshot(`
"
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

FAIL basic.test.ts > should fail with descriptive error
Error: Fixture "setup" returned without calling "use". Make sure to call "use" in every code path of the fixture function.
❯ basic.test.ts:2:27
1| await (() => {
2| const extendedTest = it.extend({
| ^
3| value: void 0,
4| setup: [
❯ basic.test.ts:16:1

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

"
`)
})

test('can import file fixture inside the local fixture', async () => {
const { stderr, fixtures, tests } = await runFixtureTests(({ log }) => it.extend<{
file: string
Expand Down
Loading