Skip to content

Commit 424c818

Browse files
haasonsaasclaude
andcommitted
fix: resolve mock issues in test files
- use vi.hoisted() to define mock functions before vi.mock runs - replace vi.mocked(fs.*) with direct mock function references - add default exports to child_process and util mocks - fix research-mcp.test.ts, github-mcp.test.ts, mcp-utils.test.ts all 278 tests now pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent cc41d56 commit 424c818

File tree

3 files changed

+96
-64
lines changed

3 files changed

+96
-64
lines changed

src/lib/github-mcp.test.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,34 @@
22
* Tests for GitHub MCP Server
33
*/
44
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5-
import fs from "fs/promises";
5+
6+
// Define mock functions using vi.hoisted() so they're available when vi.mock runs
7+
const { mockMkdir, mockWriteFile, mockAccess, mockExec, mockPromisify } = vi.hoisted(() => ({
8+
mockMkdir: vi.fn(),
9+
mockWriteFile: vi.fn(),
10+
mockAccess: vi.fn(),
11+
mockExec: vi.fn(),
12+
mockPromisify: vi.fn(() => vi.fn()),
13+
}));
614

715
// Mock modules
8-
vi.mock("fs/promises");
16+
vi.mock("fs/promises", () => ({
17+
default: {
18+
mkdir: mockMkdir,
19+
writeFile: mockWriteFile,
20+
access: mockAccess,
21+
},
22+
mkdir: mockMkdir,
23+
writeFile: mockWriteFile,
24+
access: mockAccess,
25+
}));
926
vi.mock("child_process", () => ({
10-
exec: vi.fn(),
27+
default: { exec: mockExec },
28+
exec: mockExec,
1129
}));
1230
vi.mock("util", () => ({
13-
promisify: vi.fn(() => vi.fn()),
31+
default: { promisify: mockPromisify },
32+
promisify: mockPromisify,
1433
}));
1534

1635
// Mock fetch globally
@@ -46,9 +65,9 @@ describe("github-mcp", () => {
4665

4766
beforeEach(() => {
4867
vi.clearAllMocks();
49-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
50-
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
51-
vi.mocked(fs.access).mockRejectedValue(new Error("ENOENT"));
68+
mockMkdir.mockResolvedValue(undefined);
69+
mockWriteFile.mockResolvedValue(undefined);
70+
mockAccess.mockRejectedValue(new Error("ENOENT"));
5271
});
5372

5473
afterEach(() => {

src/lib/mcp-utils.test.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* Tests for MCP Utilities
33
*/
44
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5-
import fs from "fs/promises";
65
import {
76
generatePythonLibrary,
87
RESEARCH_PYTHON_TOOLS,
@@ -11,12 +10,27 @@ import {
1110
DATA_PYTHON_TOOLS,
1211
} from "./mcp-utils";
1312

13+
// Define mock functions using vi.hoisted() so they're available when vi.mock runs
14+
const { mockMkdir, mockWriteFile } = vi.hoisted(() => ({
15+
mockMkdir: vi.fn(),
16+
mockWriteFile: vi.fn(),
17+
}));
18+
1419
// Mock fs module for testing
15-
vi.mock("fs/promises");
20+
vi.mock("fs/promises", () => ({
21+
default: {
22+
mkdir: mockMkdir,
23+
writeFile: mockWriteFile,
24+
},
25+
mkdir: mockMkdir,
26+
writeFile: mockWriteFile,
27+
}));
1628

1729
describe("mcp-utils", () => {
1830
beforeEach(() => {
1931
vi.clearAllMocks();
32+
mockMkdir.mockResolvedValue(undefined);
33+
mockWriteFile.mockResolvedValue(undefined);
2034
});
2135

2236
afterEach(() => {
@@ -166,9 +180,6 @@ describe("mcp-utils", () => {
166180

167181
describe("generatePythonLibrary", () => {
168182
it("should create directory and write file", async () => {
169-
const mockMkdir = vi.mocked(fs.mkdir).mockResolvedValue(undefined);
170-
const mockWriteFile = vi.mocked(fs.writeFile).mockResolvedValue(undefined);
171-
172183
await generatePythonLibrary(RESEARCH_PYTHON_TOOLS, "/tmp/test/lib.py");
173184

174185
expect(mockMkdir).toHaveBeenCalledWith("/tmp/test", { recursive: true });
@@ -177,9 +188,8 @@ describe("mcp-utils", () => {
177188

178189
it("should include header with dependencies", async () => {
179190
let writtenContent = "";
180-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
181-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
182-
writtenContent = content as string;
191+
mockWriteFile.mockImplementation(async (_path: string, content: string) => {
192+
writtenContent = content;
183193
});
184194

185195
await generatePythonLibrary(RESEARCH_PYTHON_TOOLS, "/tmp/test/lib.py");
@@ -191,9 +201,8 @@ describe("mcp-utils", () => {
191201

192202
it("should include all tool functions", async () => {
193203
let writtenContent = "";
194-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
195-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
196-
writtenContent = content as string;
204+
mockWriteFile.mockImplementation(async (_path: string, content: string) => {
205+
writtenContent = content;
197206
});
198207

199208
await generatePythonLibrary(RESEARCH_PYTHON_TOOLS, "/tmp/test/lib.py");
@@ -205,9 +214,8 @@ describe("mcp-utils", () => {
205214

206215
it("should include utility functions", async () => {
207216
let writtenContent = "";
208-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
209-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
210-
writtenContent = content as string;
217+
mockWriteFile.mockImplementation(async (_path: string, content: string) => {
218+
writtenContent = content;
211219
});
212220

213221
await generatePythonLibrary(RESEARCH_PYTHON_TOOLS, "/tmp/test/lib.py");
@@ -223,9 +231,8 @@ describe("mcp-utils", () => {
223231

224232
it("should include caching utilities", async () => {
225233
let writtenContent = "";
226-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
227-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
228-
writtenContent = content as string;
234+
mockWriteFile.mockImplementation(async (_path: string, content: string) => {
235+
writtenContent = content;
229236
});
230237

231238
await generatePythonLibrary(RESEARCH_PYTHON_TOOLS, "/tmp/test/lib.py");
@@ -237,9 +244,8 @@ describe("mcp-utils", () => {
237244

238245
it("should handle empty tools array", async () => {
239246
let writtenContent = "";
240-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
241-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
242-
writtenContent = content as string;
247+
mockWriteFile.mockImplementation(async (_path: string, content: string) => {
248+
writtenContent = content;
243249
});
244250

245251
await generatePythonLibrary([], "/tmp/test/lib.py");
@@ -251,9 +257,8 @@ describe("mcp-utils", () => {
251257

252258
it("should collect all dependencies", async () => {
253259
let writtenContent = "";
254-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
255-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
256-
writtenContent = content as string;
260+
mockWriteFile.mockImplementation(async (_path: string, content: string) => {
261+
writtenContent = content;
257262
});
258263

259264
const toolsWithDeps = [
@@ -266,9 +271,8 @@ describe("mcp-utils", () => {
266271

267272
it("should generate valid Python syntax", async () => {
268273
let writtenContent = "";
269-
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
270-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
271-
writtenContent = content as string;
274+
mockWriteFile.mockImplementation(async (_path: string, content: string) => {
275+
writtenContent = content;
272276
});
273277

274278
await generatePythonLibrary(

src/lib/research-mcp.test.ts

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
*/
44
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
55

6-
// Create mock functions
7-
const mockMkdir = vi.fn().mockResolvedValue(undefined);
8-
const mockWriteFile = vi.fn().mockResolvedValue(undefined);
9-
const mockReadFile = vi.fn().mockResolvedValue("");
10-
const mockReaddir = vi.fn().mockResolvedValue([]);
11-
const mockStat = vi.fn().mockResolvedValue({ isFile: () => true });
6+
// Define mock functions using vi.hoisted() so they're available when vi.mock runs
7+
const { mockMkdir, mockWriteFile, mockReadFile, mockReaddir, mockStat, mockExec, mockPromisify } =
8+
vi.hoisted(() => ({
9+
mockMkdir: vi.fn(),
10+
mockWriteFile: vi.fn(),
11+
mockReadFile: vi.fn(),
12+
mockReaddir: vi.fn(),
13+
mockStat: vi.fn(),
14+
mockExec: vi.fn(),
15+
mockPromisify: vi.fn(() => vi.fn()),
16+
}));
1217

1318
// Mock modules before importing
1419
vi.mock("fs/promises", () => ({
@@ -26,15 +31,14 @@ vi.mock("fs/promises", () => ({
2631
stat: mockStat,
2732
}));
2833
vi.mock("child_process", () => ({
29-
exec: vi.fn(),
34+
default: { exec: mockExec },
35+
exec: mockExec,
3036
}));
3137
vi.mock("util", () => ({
32-
promisify: vi.fn(() => vi.fn()),
38+
default: { promisify: mockPromisify },
39+
promisify: mockPromisify,
3340
}));
3441

35-
// Import fs after mocking
36-
import fs from "fs/promises";
37-
3842
// Mock fetch globally
3943
const mockFetch = vi.fn();
4044
vi.stubGlobal("fetch", mockFetch);
@@ -142,8 +146,8 @@ describe("research-mcp", () => {
142146
],
143147
});
144148

145-
expect(fs.mkdir).toHaveBeenCalled();
146-
expect(fs.writeFile).toHaveBeenCalled();
149+
expect(mockMkdir).toHaveBeenCalled();
150+
expect(mockWriteFile).toHaveBeenCalled();
147151
expect(result.content[0].text).toContain("Created notebook");
148152
expect(result.content[0].text).toContain("2 cells added");
149153
});
@@ -187,7 +191,7 @@ describe("research-mcp", () => {
187191
};
188192

189193
beforeEach(() => {
190-
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(sampleNotebook));
194+
mockReadFile.mockResolvedValue(JSON.stringify(sampleNotebook));
191195
});
192196

193197
it("should read notebook contents", async () => {
@@ -248,7 +252,7 @@ describe("research-mcp", () => {
248252
};
249253

250254
beforeEach(() => {
251-
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(sampleNotebook));
255+
mockReadFile.mockResolvedValue(JSON.stringify(sampleNotebook));
252256
});
253257

254258
it("should provide notebook summary", async () => {
@@ -292,12 +296,12 @@ describe("research-mcp", () => {
292296
};
293297

294298
beforeEach(() => {
295-
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(existingNotebook));
299+
mockReadFile.mockResolvedValue(JSON.stringify(existingNotebook));
296300
});
297301

298302
it("should add cells at end by default", async () => {
299303
let writtenContent = "";
300-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
304+
mockWriteFile.mockImplementation(async (_path, content) => {
301305
writtenContent = content as string;
302306
});
303307

@@ -314,7 +318,7 @@ describe("research-mcp", () => {
314318

315319
it("should add cells at start when specified", async () => {
316320
let writtenContent = "";
317-
vi.mocked(fs.writeFile).mockImplementation(async (_path, content) => {
321+
mockWriteFile.mockImplementation(async (_path, content) => {
318322
writtenContent = content as string;
319323
});
320324

@@ -335,7 +339,7 @@ describe("research-mcp", () => {
335339

336340
describe("error handling", () => {
337341
it("should handle file not found", async () => {
338-
vi.mocked(fs.readFile).mockRejectedValue(new Error("ENOENT: file not found"));
342+
mockReadFile.mockRejectedValue(new Error("ENOENT: file not found"));
339343

340344
const result = await notebookHandler({
341345
action: "read",
@@ -347,7 +351,7 @@ describe("research-mcp", () => {
347351
});
348352

349353
it("should handle invalid JSON", async () => {
350-
vi.mocked(fs.readFile).mockResolvedValue("not valid json");
354+
mockReadFile.mockResolvedValue("not valid json");
351355

352356
const result = await notebookHandler({
353357
action: "read",
@@ -511,7 +515,7 @@ describe("research-mcp", () => {
511515
outputPath: "paper.pdf",
512516
});
513517

514-
expect(fs.writeFile).toHaveBeenCalled();
518+
expect(mockWriteFile).toHaveBeenCalled();
515519
expect(result.content[0].text).toContain("Downloaded");
516520
expect(result.content[0].text).toContain("paper.pdf");
517521
});
@@ -656,8 +660,8 @@ describe("research-mcp", () => {
656660
notebookPath: "review.ipynb",
657661
});
658662

659-
expect(fs.writeFile).toHaveBeenCalled();
660-
const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
663+
expect(mockWriteFile).toHaveBeenCalled();
664+
const writeCall = mockWriteFile.mock.calls[0];
661665
expect(writeCall[0]).toContain("review.ipynb");
662666
});
663667

@@ -714,18 +718,23 @@ describe("research-mcp", () => {
714718

715719
beforeEach(async () => {
716720
// Setup exec mock
717-
const { exec } = await import("child_process");
718721
mockExecAsync = vi.fn().mockResolvedValue({ stdout: "", stderr: "" });
719-
vi.mocked(exec).mockImplementation((_cmd, _opts, callback) => {
720-
if (callback) {
721-
callback(null, "", "");
722+
mockExec.mockImplementation(
723+
(
724+
_cmd: string,
725+
_opts: unknown,
726+
callback?: (err: Error | null, stdout: string, stderr: string) => void
727+
) => {
728+
if (callback) {
729+
callback(null, "", "");
730+
}
731+
return {} as unknown;
722732
}
723-
return {} as ReturnType<typeof exec>;
724-
});
733+
);
725734

726735
// Re-mock promisify to return our mock
727-
const { promisify } = await import("util");
728-
vi.mocked(promisify).mockReturnValue(mockExecAsync);
736+
// @ts-expect-error - Mock type complexity with promisify
737+
mockPromisify.mockReturnValue(mockExecAsync);
729738

730739
// Need to re-import to get fresh module with new mocks
731740
vi.resetModules();

0 commit comments

Comments
 (0)