Skip to content

ADR-19: Vitest 4.0+ test.for/it.for API Support ​

🇰🇷 한국어 버전

DateAuthorRepos
2026-01-03@specvitalcore

Context ​

Problem Statement ​

SpecVital Core parser's static AST analysis did not recognize Vitest's test.for/it.for API, causing significant accuracy degradation when parsing modern Vitest codebases.

Discovery ​

Validation against vitejs/vite v7.3.0 revealed:

MetricValue
Ground Truth (CLI)703 tests
Parser Result583 tests
Delta-120 (-17.1%)

Root cause: 120 tests using test.for API were undetected.

Background ​

Vitest 2.0 introduced test.for/it.for as an alternative to test.each with key differences:

Aspecttest.eachtest.for
Argument spreadingSpreads arraysNo spreading
TestContext accessNot availableAvailable as 2nd param
Concurrent snapshotsNot supportedSupported
Jest compatibilityYesVitest-only

Syntax difference:

typescript
// test.each spreads array arguments
test.each([
  [1, 1, 2],
  [1, 2, 3],
])("add(%i, %i) -> %i", (a, b, expected) => {
  expect(a + b).toBe(expected);
});

// test.for does NOT spread - requires destructuring
test.for([
  [1, 1, 2],
  [1, 2, 3],
])("add(%i, %i) -> %i", ([a, b, expected]) => {
  expect(a + b).toBe(expected);
});

The .for API addresses test.each limitations around TestContext and fixtures, enabling concurrent snapshot testing:

typescript
test.concurrent.for([
  [1, 1],
  [1, 2],
])("add(%i, %i)", ([a, b], { expect }) => {
  expect(a + b).matchSnapshot();
});

Requirements ​

  1. Detect test.for/it.for/describe.for patterns in Vitest files
  2. Apply same counting policy as .each() (ADR-02)
  3. Support chained modifiers: test.concurrent.for, test.skip.for, test.only.for
  4. Minimize changes to shared jstest module (ADR-08)

Decision ​

Extend existing .each() infrastructure to support .for modifier.

The jstest shared module's parameterized test handling is extended to recognize .for as an additional modifier alongside .each. This applies the established Dynamic Test Counting Policy (ADR-02): parameterized tests count as 1 regardless of runtime iterations.

Implementation ​

Minimal changes to three files in pkg/parser/strategies/shared/jstest/:

go
// constants.go
const (
    ModifierFor  = "for"    // New
    ModifierEach = "each"
    // ... existing modifiers
)

// helpers.go - Include .for in modifier detection
func ParseSimpleMemberExpression(node *sitter.Node, source []byte) string {
    // Returns "test.for", "describe.for", etc.
}

// parser.go - Route .for through existing processing
switch funcName {
case FuncDescribe + "." + ModifierEach, FuncDescribe + "." + ModifierFor:
    ProcessEachSuites(...)
case FuncIt + "." + ModifierEach, FuncIt + "." + ModifierFor:
    ProcessEachTests(...)
}

Supported Patterns ​

PatternDescription
test.for([...])('name', cb)Basic parameterized test
it.for([...])('name', cb)Alias for test.for
describe.for([...])('name', cb)Parameterized suite
test.concurrent.for([...])('name', cb)Concurrent parameterized test
test.skip.for([...])('name', cb)Skipped parameterized test
test.only.for([...])('name', cb)Focused parameterized test

Counting Policy ​

Per ADR-02, all parameterized test patterns count as 1:

PatternParser CountRationale
test.for([a,b,c])1Static analysis cannot evaluate runtime count
test.each([a,b,c])1Same policy for consistency
test.concurrent.for([...])1Modifier chain does not change policy

Options Considered ​

Option A: Extend Existing .each() Infrastructure (Selected) ​

Add .for as an additional modifier in the existing parameterized test handling.

Pros:

  • Minimal code changes (3 files)
  • Leverages battle-tested .each() infrastructure
  • Consistent with Dynamic Test Counting Policy (ADR-02)
  • All JavaScript frameworks benefit via shared module (ADR-08)
  • Single code path for parameterized tests

Cons:

  • Ignores semantic differences between .for and .each
  • Future .for-specific features may require divergence

Option B: Create Separate test.for Parser ​

Implement independent parsing logic for test.for/it.for patterns.

Pros:

  • Clean separation of concerns
  • No risk of .each() regression
  • Can implement .for-specific optimizations

Cons:

  • ~70% code duplication with .each() handling
  • Violates DRY principle and Shared Parser Modules pattern (ADR-08)
  • Bug fixes must be applied separately to each path
  • Maintenance burden increase

Option C: Different Counting Policy for test.for ​

Attempt to count actual iterations for .for patterns by parsing array arguments.

Pros:

  • More accurate counts for simple literal cases
  • Better alignment with user expectations

Cons:

  • Violates established Dynamic Test Counting Policy (ADR-02)
  • Inconsistent behavior between similar APIs
  • Cannot handle variable references (same limitation)
  • User confusion from different counting rules

Option D: Runtime Detection via Vitest API ​

Use Vitest's test collection API for accurate runtime counts.

Pros:

  • 100% accuracy for all patterns
  • No static analysis limitations

Cons:

  • Fundamentally changes core's static-only architecture
  • Requires test environment setup
  • Security implications from code execution
  • Performance impact
  • Violates core architectural principle (ADR-03)

Consequences ​

Positive ​

  1. Accuracy Restoration

    • vitejs/vite detection accuracy returns to acceptable range
    • Modern Vitest codebases correctly parsed
  2. Consistency

    • Same counting behavior for .each() and .for() patterns
    • Users experience predictable behavior across parameterized APIs
  3. Shared Module Benefit

    • All jstest consumers (Jest, Vitest, Mocha, Cypress, Playwright) gain capability
    • Prepared for potential .for adoption by other frameworks
  4. Maintainability

    • Single code path for all parameterized test handling
    • Bug fixes in shared logic benefit all patterns

Negative ​

  1. Semantic Simplification

    • Argument spreading differences between .for and .each are ignored
    • Mitigation: Counting policy treats both as 1, making semantic difference irrelevant for count accuracy
  2. Potential Future Divergence

    • If .for gains features requiring different handling, refactoring may be needed
    • Mitigation: Current approach does not preclude future separation into Option B if warranted

References ​

Open-source test coverage insights