fix(McpHub): resolve persistently flaky McpHub.spec.ts tests after Vitest 4 upgrade#666
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe ChangesMcpHub test synchronization refactor
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Related GitHub Issue
Closes: #489
Description
This PR fixes 35 persistently failing tests in
services/mcp/__tests__/McpHub.spec.tsthat regressed after the Vitest 4 upgrade. Three compounding root causes were identified and fixed:1.
import fsvsimport * as fsmock instance mismatchMcpHub.tsusesimport * as fs from "fs/promises"(namespace import). The test usedimport fs from "fs/promises"(default import). Thevi.mockfactory created separatevi.fn()instances for the default and named exports, sovi.mocked(fs.readFile).mockResolvedValue(...)in tests was controlling a dead mock — the SUT called the named-export binding which still returned the factory default"{}". Fix: align toimport * as fsand share allvi.fn()instances via a closure:const shared = {...}; return { default: shared, ...shared }.2. Duplicate
vi.mock("fs/promises")silently overriding the factoryA bare
vi.mock("fs/promises")(no factory) at line ~96 was silently overriding the factory mock above it with Vitest's auto-mock, replacing all carefully constructedvi.fn()instances with new stubs. Removed.3. Non-async
beforeEachracing against async initializationThe outer
beforeEachwas synchronous, so test bodies started running whileinitializationPromise(fromPromise.all([initializeGlobalMcpServers(), initializeProjectMcpServers()])) was still in flight. Fix: makebeforeEachasync and awaitmcpHub.waitUntilReady().Additional fixes:
await new Promise(r => setTimeout(r, 100))timing hacks withawait mcpHub.waitUntilReady()(deterministic)fs.readFilemock beforenew McpHub()and removed direct calls to the privateinitializeGlobalMcpServers()methodawait Promise.resolve(); await Promise.resolve()microtask tick-counting withvi.waitFor()/vi.advanceTimersByTimeAsync(0)to decouple tests from the SUT's internal async structureexpect(firstEntry.unsubscribe).toHaveBeenCalledOnce()to the OAuth watcher-replacement test, closing a mutation gap (required changingmockReturnValue(vi.fn())→mockImplementation(() => vi.fn())so eachonDidChangecall gets its own spy)Test Procedure
All 62 tests pass. Verified across 3 consecutive runs before and after the fix.
Pre-Submission Checklist
Summary by CodeRabbit