{"record":{"id":"a0c5a7444585455e","repo":"vitest-dev/vitest","slug":"circular-fixture-dependency-detected-fixture-na","errorCode":null,"errorMessage":"Circular fixture dependency detected: ${fixture.name} <- ${[...depSet].reverse().map(d => d.name).join(' <- ')}","messagePattern":"Circular fixture dependency detected: (.+?) <- (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vitest/src/runtime/runner/fixture.ts","lineNumber":573,"sourceCode":"  usedFixtures: TestFixtureItem[],\n  registrations: FixtureRegistrations,\n  depSet = new Set<TestFixtureItem>(),\n  pendingFixtures: TestFixtureItem[] = [],\n) {\n  usedFixtures.forEach((fixture) => {\n    if (pendingFixtures.includes(fixture)) {\n      return\n    }\n    if (!isFixtureFunction(fixture.value) || !fixture.deps) {\n      pendingFixtures.push(fixture)\n      return\n    }\n    if (depSet.has(fixture)) {\n      if (fixture.parent) {\n        fixture = fixture.parent\n      }\n      else {\n        throw new Error(\n          `Circular fixture dependency detected: ${fixture.name} <- ${[...depSet]\n            .reverse()\n            .map(d => d.name)\n            .join(' <- ')}`,\n        )\n      }\n    }\n\n    depSet.add(fixture)\n    resolveDeps(\n      Array.from(fixture.deps, n => n === fixture.name ? fixture.parent : registrations.get(n)).filter(n => !!n),\n      registrations,\n      depSet,\n      pendingFixtures,\n    )\n    pendingFixtures.push(fixture)\n    depSet.clear()\n  })","sourceCodeStart":555,"sourceCodeEnd":591,"githubUrl":"https://github.com/vitest-dev/vitest/blob/1fa9837ec26533512fdcad8baebf249771bd340a/packages/vitest/src/runtime/runner/fixture.ts#L555-L591","documentation":"Thrown by resolveDeps when following a fixture's dependency chain leads back to a fixture already in the active resolution set (depSet). Vitest builds a topological order of fixture instantiation; a cycle makes a correct order impossible. The message prints the chain so you can see which fixtures form the loop.","triggerScenarios":"Fixture A depends on B, B depends on C, C depends on A (transitive cycle); a fixture that depends on a name that resolves to itself via parent overrides without a base implementation. The parent branch only avoids the throw if a base fixture exists.","commonSituations":"Refactoring fixtures so two start mutual-depending; splitting one fixture into two that cross-reference; using `test.fn` per-test overrides that reintroduce a cycle.","solutions":["Break the cycle by extracting the shared dependency into a third fixture that both depend on.","Use the per-test override parent (`fixture.parent`) correctly so overrides extend rather than replace.","Review the printed chain `<-` list to identify the exact pair to sever."],"exampleFix":"// before\nconst a = test.fn(async ({ b, use }) => use(b + 1))\nconst b = test.fn(async ({ a, use }) => use(a + 1)) // a <-> b cycle\n\n// after\nconst base = test.fn(async ({ use }) => use(1))\nconst a = test.fn(async ({ base, use }) => use(base + 1))\nconst b = test.fn(async ({ base, use }) => use(base + 1))","handlingStrategy":"validation","validationCode":"function detectCycle(fixtures: Map<string, string[]>): string[] | null {\n  const visited = new Set<string>()\n  const stack = new Set<string>()\n  function dfs(name: string): string[] | null {\n    if (stack.has(name)) return [...stack, name]\n    if (visited.has(name)) return null\n    visited.add(name); stack.add(name)\n    for (const dep of fixtures.get(name) ?? []) {\n      const cycle = dfs(dep)\n      if (cycle) return cycle\n    }\n    stack.delete(name)\n    return null\n  }\n  for (const name of fixtures.keys()) {\n    const c = dfs(name)\n    if (c) return c\n  }\n  return null\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runTest()\n} catch (e) {\n  if (/Circular fixture dependency/.test(e.message)) {\n    // inspect the printed chain, refactor fixtures\n  } else throw e\n}","preventionTips":["Extract shared dependencies into a base fixture rather than cross-referencing.","Use per-test overrides via test.fn with parent correctly to extend, not replace.","Visualize fixture deps when adding a new dependency."],"tags":["fixtures","dependency-injection","cycle"],"backgroundTag":null,"analyzedSha":"1fa9837ec26533512fdcad8baebf249771bd340a","analyzedAt":"2026-08-11T16:11:39.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}