Skip to content

fix: detect fixture that returns without calling use (#9831)#9861

Merged
sheremet-va merged 3 commits intovitest-dev:mainfrom
hamsurang:fix/9831-fixture-use-error-message
Mar 16, 2026
Merged

fix: detect fixture that returns without calling use (#9831)#9861
sheremet-va merged 3 commits intovitest-dev:mainfrom
hamsurang:fix/9831-fixture-use-error-message

Conversation

@oilater
Copy link
Contributor

@oilater oilater commented Mar 13, 2026

Description

Resolves #9831

When a fixture function returns without calling use(), Vitest currently waits for a timeout and shows a generic Test timed out error, making it difficult to debug the root cause.

This PR detects the case immediately and rejects with a descriptive error:

Error: Fixture returned without calling "use". 
Make sure to call "use" in every code path of the fixture function.

Implementation

  • Added .then().catch() chain in resolveFixtureFunction.then() detects when the fixture resolves without use() being called, .catch() handles errors thrown inside the fixture (existing behavior).
스크린샷 2026-03-14 오전 2 23 18

Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. If the feature is substantial or introduces breaking changes without a discussion, PR might be closed.
  • Ideally, include a test that fails without this PR but passes with it.
  • Please, don't make changes to pnpm-lock.yaml unless you introduce a new test example.
  • Please check Allow edits by maintainers to make review process faster. Note that this option is not available for repositories that are owned by Github organizations.

Tests

  • Run the tests with pnpm test:ci.

Documentation

  • If you introduce new functionality, document it. You can run documentation with pnpm run docs command. No documentation needed — this only improves an error message.

Changesets

  • Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with feat:, fix:, perf:, docs:, or chore:.

Instead of waiting for a timeout, immediately reject with a
descriptive error when a fixture function returns without calling
`use()`.
@oilater oilater force-pushed the fix/9831-fixture-use-error-message branch from 6d46e2a to 81a7c8a Compare March 13, 2026 17:38
AriPerkkio
AriPerkkio previously approved these changes Mar 13, 2026
Copy link
Member

@AriPerkkio AriPerkkio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks for the fix! 💯

@AriPerkkio AriPerkkio requested a review from sheremet-va March 13, 2026 20:16
@oilater
Copy link
Contributor Author

oilater commented Mar 13, 2026

Looks good to me, thanks for the fix! 💯

Thank you so much! 😊

sheremet-va
sheremet-va previously approved these changes Mar 14, 2026
Copy link
Member

@sheremet-va sheremet-va left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, but the error doesn't have a proper stack trace. It would be nice to point to the fixture declaration, but I don't know how to calculate the error trace 🤔 Does Error.captureStackTrace on the fixture function works?

I would expect something like

Error: Fixture returned without calling "use". Make sure to call "use" in every code path of the fixture function.

    async ({ value }, use) => {
    ^

@oilater
Copy link
Contributor Author

oilater commented Mar 14, 2026

This looks good to me, but the error doesn't have a proper stack trace. It would be nice to point to the fixture declaration, but I don't know how to calculate the error trace 🤔 Does Error.captureStackTrace on the fixture function works?

I would expect something like

Error: Fixture returned without calling "use". Make sure to call "use" in every code path of the fixture function.

    async ({ value }, use) => {
    ^

Thanks for the feedback!
I implemented the stack trace using the same Symbol-based pattern as hooks.ts

Since the fixture function definition isn't a call frame, pointing to the exact line isn't possible at runtime 🤔
but it can point to the base.extend(...) call site like below.

스크린샷 2026-03-15 오전 5 20 18

How about also including the fixture name in the error message like this?

Error: Fixture "setup" returned without calling "use". Make sure to call "use" in every code path of the fixture function.

Let me know how you'd like to proceed, and I'll update this PR!

if (!isUseFnArgResolved) {
useFnArgPromise.reject(
new Error(
'Fixture returned without calling "use". Make sure to call "use" in every code path of the fixture function.',
Copy link
Contributor

@hi-ogawa hi-ogawa Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it surface fixture name? For the test case, it would be setup.

EDIT: ah sorry, you've already suggested that #9861 (comment).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, Done in 09b3f1c!


exports[`should fail test-extend/fixture-without-destructuring.test.ts 1`] = `"FixtureParseError: The 1st argument inside a fixture must use object destructuring pattern, e.g. ({ task } => {}). Instead, received "context"."`;

exports[`should fail test-extend/fixture-without-use.test.ts 1`] = `"Error: Fixture returned without calling "use". Make sure to call "use" in every code path of the fixture function."`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should favor runInlineTests + stderr snapshot pattern, so we can view full error output (even if we don't have stack right now, it might in the future). Probably we can put it somewhere around

test('test fixture cannot import from file fixture', async () => {
const { stderr } = await runInlineTests({
'basic.test.ts': () => {
const extendedTest = it.extend<{
file: string
local: string
}>({
local: ({}, use) => use('local'),
file: [({ local }, use) => use(local), { scope: 'file' }],
})
extendedTest('not working', ({ file: _file }) => {})
},
}, { globals: true })
expect(stderr).toMatchInlineSnapshot(`
"
⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL basic.test.ts [ basic.test.ts ]
FixtureDependencyError: The file "file" fixture cannot depend on a test fixture "local".
❯ basic.test.ts:2:27
1| await (() => {
2| const extendedTest = it.extend({
| ^
3| local: ({}, use) => use("local"),
4| file: [({ local }, use) => use(local), { scope: "file" }]
❯ basic.test.ts:8:1
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
"
`)
})

- Capture registration-time stack trace using Symbol-based pattern from hooks.ts
- Include fixture name in error message for easier identification
- Move test to runInlineTests + stderr inline snapshot pattern
@oilater oilater dismissed stale reviews from sheremet-va and AriPerkkio via 09b3f1c March 16, 2026 03:57
@oilater
Copy link
Contributor Author

oilater commented Mar 16, 2026

Updated in 09b3f1c !

  • Added stack trace pointing to base.extend(...) call site
  • Included fixture name in the error message
  • Moved test to runInlineTests + stderr inline snapshot pattern in scoped-fixtures.test.ts
스크린샷 2026-03-16 오후 12 56 01

Copy link
Contributor

@hi-ogawa hi-ogawa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@sheremet-va sheremet-va merged commit 633ae23 into vitest-dev:main Mar 16, 2026
15 of 16 checks passed
@sheremet-va
Copy link
Member

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve error message when fixture doesn't call use before timeout

4 participants