vitest-dev/vitest · error · Error
Circular fixture dependency detected: ${fixture.name} <- ${[
Error message
Circular fixture dependency detected: ${fixture.name} <- ${[...depSet].reverse().map(d => d.name).join(' <- ')} What it means
Vitest resolves fixture dependencies by walking the dependency graph in `resolveDeps` (fixture.ts:554). If fixture A depends on B which depends back on A (directly or transitively) and there is no `parent` base implementation to break the cycle, the resolver detects the revisit and throws this error with the full cycle chain formatted as `A <- B <- A`.
Source
Thrown at packages/vitest/src/runtime/runner/fixture.ts:573
usedFixtures: TestFixtureItem[],
registrations: FixtureRegistrations,
depSet = new Set<TestFixtureItem>(),
pendingFixtures: TestFixtureItem[] = [],
) {
usedFixtures.forEach((fixture) => {
if (pendingFixtures.includes(fixture)) {
return
}
if (!isFixtureFunction(fixture.value) || !fixture.deps) {
pendingFixtures.push(fixture)
return
}
if (depSet.has(fixture)) {
if (fixture.parent) {
fixture = fixture.parent
}
else {
throw new Error(
`Circular fixture dependency detected: ${fixture.name} <- ${[...depSet]
.reverse()
.map(d => d.name)
.join(' <- ')}`,
)
}
}
depSet.add(fixture)
resolveDeps(
Array.from(fixture.deps, n => n === fixture.name ? fixture.parent : registrations.get(n)).filter(n => !!n),
registrations,
depSet,
pendingFixtures,
)
pendingFixtures.push(fixture)
depSet.clear()
})View on GitHub (pinned to d568f8ce37)
Solutions
- Break the cycle by introducing a shared base fixture that both depend on instead of each other.
- Use `test.extend` parent/base implementation: redefining a fixture by the same name creates a `parent` that breaks the self-dependency.
- Restructure so dependencies flow one direction (leaf fixtures have no fixture deps).
Example fix
// before: a depends on b, b depends on a
test.extend({
a: ({ b }, use) => use(b + 1),
b: ({ a }, use) => use(a + 1),
})
// after: shared base
test.extend({
base: ({}, use) => use(0),
a: ({ base }, use) => use(base + 1),
b: ({ base }, use) => use(base + 1),
}) Defensive patterns
Strategy: validation
Validate before calling
// Topologically validate fixture dependencies before extend.
function hasCycle(defs: Record<string, string[]>): boolean {
const visited = new Map<string, 'visiting' | 'done'>()
function dfs(n: string): boolean {
const s = visited.get(n)
if (s === 'visiting') return true
if (s === 'done') return false
visited.set(n, 'visiting')
for (const d of defs[n] ?? []) if (dfs(d)) return true
visited.set(n, 'done')
return false
}
return Object.keys(defs).some(dfs)
} Prevention
- Keep fixture dependencies acyclic: leaf fixtures depend on nothing.
- When overriding a fixture by name, ensure the override still flows dependencies one direction.
- Draw the dependency graph when splitting fixtures.
When it happens
Trigger: Defining `a: ({ b }, use) => use(...)` and `b: ({ a }, use) => use(...)`; any transitive cycle across three or more fixtures; overriding a fixture to depend on one that ultimately depends on the override target.
Common situations: Splitting a monolithic fixture into two that reference each other; overriding fixtures in nested suites creating a cycle; refactoring fixtures without checking the dependency direction.
Related errors
- Cannot resolve user fixtures. See errors for more informatio
- onCleanup can only be called once per fixture. Define separa
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/a0c5a7444585455e.json.
Report an issue: GitHub.