Skip to content

🐛(vitest) Fix runtime exception with test.each#6804

Merged
dubzzz merged 17 commits into
mainfrom
claude/issue-6798-20260331-2201
Apr 28, 2026
Merged

🐛(vitest) Fix runtime exception with test.each#6804
dubzzz merged 17 commits into
mainfrom
claude/issue-6798-20260331-2201

Conversation

@dubzzz

@dubzzz dubzzz commented Mar 31, 2026

Copy link
Copy Markdown
Owner

When building the enriched test function, the each method was extracted from testFnExtended (created by createTaskCollector) without proper this binding. This caused a 'Cannot read properties of undefined (reading withContext)' error when calling test.each().

Fix: use the original vitest testFn's each method with proper binding, since each is a standard vitest feature that doesn't need fast-check wrapping.

Fixes #6798

Description

Fixes #issue-number

Checklist

Don't delete this checklist and make sure you do the following before opening the PR

  • I have a full understanding of every line in this PR — whether the code was hand-written, AI-generated, copied from external sources or produced by any other tool
  • I flagged the impact of my change (minor / patch / major) either by running pnpm run bump or by following the instructions from the changeset bot
  • I kept this PR focused on a single concern and did not bundle unrelated changes
  • I followed the gitmoji specification for the name of the PR, including the package scope (e.g. 🐛(vitest) Something...) when the change targets a package other than fast-check
  • I added relevant tests and they would have failed without my PR (when applicable)

…nction

When building the enriched test function, the each method was extracted from
testFnExtended (created by createTaskCollector) without proper this binding.
This caused a 'Cannot read properties of undefined (reading withContext)'
error when calling test.each().

Fix: use the original vitest testFn's each method with proper binding, since
each is a standard vitest feature that doesn't need fast-check wrapping.

Fixes #6798

Co-authored-by: Nicolas DUBIEN <[email protected]>
@changeset-bot

changeset-bot Bot commented Mar 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e2e0f70

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@fast-check/vitest Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@dubzzz dubzzz changed the title 🐛(vitest) Fix test.each TypeError by binding each to original test function 🐛(vitest) Fix runtime exception with test.each Mar 31, 2026
Comment thread packages/vitest/src/internals/TestBuilder.ts
@pkg-pr-new

pkg-pr-new Bot commented Mar 31, 2026

Copy link
Copy Markdown
@fast-check/ava

npm i https://pkg.pr.new/@fast-check/ava@6804

fast-check

npm i https://pkg.pr.new/fast-check@6804

@fast-check/jest

npm i https://pkg.pr.new/@fast-check/jest@6804

@fast-check/packaged

npm i https://pkg.pr.new/@fast-check/packaged@6804

@fast-check/poisoning

npm i https://pkg.pr.new/@fast-check/poisoning@6804

@fast-check/vitest

npm i https://pkg.pr.new/@fast-check/vitest@6804

@fast-check/worker

npm i https://pkg.pr.new/@fast-check/worker@6804

commit: e2e0f70

Apply the same .bind(testFn) fix to the non-each branch of the loop,
ensuring methods like skip, only, concurrent etc. also retain proper
this context when accessed through the enriched test function.

Co-authored-by: Nicolas DUBIEN <[email protected]>
? buildTest((testFn as any)[key], testFnExtended[key] as any, fc, new Set([...ancestors, key]))
: testFnExtended[key];
? buildTest(((testFn as any)[key] as (...args: unknown[]) => unknown).bind(testFn), testFnExtended[key] as any, fc, new Set([...ancestors, key]))
: ((testFn as any)[key] as (...args: unknown[]) => unknown).bind(testFn);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@claude bind extended fn too?

@claude claude Bot Mar 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude finished @dubzzz's task in 40s —— View job


  • Read the code around line 149 to understand what needs binding
  • Implement the fix
  • Push the change

Added .bind(testFnExtended) to the testFnExtended[key] argument in the recursive buildTest call on line 148, so the extended fn also retains proper this context.

Change: b809fde

github-actions Bot and others added 2 commits March 31, 2026 22:26
  Applied by GitHub Action workflow: PR Format
  Run ID: 23822582479
  Target PR: 6804
@dubzzz

This comment has been minimized.

@claude

claude Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dubzzz's task in 11m 2s —— View job


  • Diagnose why 14 tests broke after binding changes
  • Fix the issue in TestBuilder.ts
  • Push the fix

Root cause

The previous commits had two issues:

  1. .bind() strips own properties: testFnExtended[key].bind(testFnExtended) creates a new bound function that loses all own properties (like skip, only, concurrent, etc.). So when buildTest recurses into it, Object.getOwnPropertyNames() finds nothing, the recursion stops early, and .prop is never added to sub-levels → test.concurrent.prop is not a function.

  2. testFn[key] can be undefined: testFn (the original vitest function) may not have all keys that testFnExtended (from createTaskCollector) has. Accessing undefined[key] then throws.

Fix

  • Don't bind testFnExtended[key] — it's only used for property enumeration and is already wrapped in a closure (line 158), so binding is unnecessary
  • Guard testFn[key] access with a null check for safety

Change: cb7a99d


|

Don't bind testFnExtended[key] as .bind() creates a new function without
own properties, breaking property enumeration in recursive calls.
Guard testFn[key] access against testFn being undefined/null.

Co-authored-by: Nicolas DUBIEN <[email protected]>
Comment thread packages/vitest/src/internals/TestBuilder.ts Outdated
github-actions Bot and others added 2 commits April 1, 2026 20:04
Bind testFnExtended[key] to preserve proper this context, using
Object.defineProperties to copy own property descriptors back onto the
bound function so that Object.getOwnPropertyNames still finds
sub-properties for recursion. Re-add null check on testFn since it
can be undefined at deeper recursion levels.

Co-authored-by: Nicolas DUBIEN <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Comment thread packages/vitest/src/internals/TestBuilder.ts Outdated
Drop Object.defineProperties wrapper and use simple .bind() matching
the pattern used for testFn binding.

Co-authored-by: Nicolas DUBIEN <[email protected]>
Comment thread packages/vitest/src/internals/TestBuilder.ts Outdated
Comment thread packages/vitest/src/internals/TestBuilder.ts Outdated
Comment thread packages/vitest/src/internals/TestBuilder.ts Outdated
Comment thread packages/vitest/src/internals/TestBuilder.ts Outdated
@rushelex

Copy link
Copy Markdown
Contributor

@dubzzz Check out PR #6910 with a fix for this PR

…er (#6910)

Fixes PR #6804.

The `each` method (e.g. `it.each` / `test.each`) in Vitest requires a
valid `this` context, as it uses `this` internally to access class
methods. When assigning via `testFn[key]`, the context was lost, leading
to errors when calling `it.each(...)`.

Fix: explicitly bind the method to `testFn` via `.bind(testFn)`,
ensuring the correct context is used when calling.
@codecov

codecov Bot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.75%. Comparing base (cabaab6) to head (e2e0f70).
⚠️ Report is 52 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6804      +/-   ##
==========================================
- Coverage   94.82%   94.75%   -0.08%     
==========================================
  Files         209      211       +2     
  Lines        5665     5778     +113     
  Branches     1489     1526      +37     
==========================================
+ Hits         5372     5475     +103     
- Misses        285      295      +10     
  Partials        8        8              
Flag Coverage Δ
tests 94.75% <ø> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dubzzz
dubzzz disabled auto-merge April 28, 2026 20:53
@dubzzz
dubzzz enabled auto-merge (squash) April 28, 2026 20:53
@dubzzz
dubzzz merged commit f95a6bd into main Apr 28, 2026
48 checks passed
@dubzzz
dubzzz deleted the claude/issue-6798-20260331-2201 branch April 28, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test.each in Vitest sees a TypeError

2 participants