Skip to content

Commit 58b2301

Browse files
committed
fix: implement proper toHaveBeenCalledWith method for Jest SmartSpy and refactor to switch statement
1 parent 216623e commit 58b2301

File tree

1 file changed

+51
-28
lines changed

1 file changed

+51
-28
lines changed

jest-setup.ts

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -65,35 +65,58 @@ function fn() {
6565
function createSmartSpy(realSpy: jest.SpyInstance): SmartSpy {
6666
return new Proxy(realSpy as object, {
6767
get(target, prop) {
68-
// Only implement the methods we actually use
69-
if (prop === 'mockImplementation') {
70-
return (fn: () => void) => {
71-
(target as jest.SpyInstance).mockImplementation(fn);
72-
return createSmartSpy(target as jest.SpyInstance);
73-
};
74-
}
75-
if (prop === 'toHaveBeenCalledWith') {
76-
return (...args: unknown[]) => {
77-
// Jest SpyInstance doesn't have toHaveBeenCalledWith as a method,
78-
// it's available through expect(spy).toHaveBeenCalledWith()
79-
// For compatibility, we'll check if the method exists and call it
80-
const spy = target as Record<string, unknown>;
81-
if (typeof spy.toHaveBeenCalledWith === 'function') {
82-
return (
83-
spy.toHaveBeenCalledWith as (...args: unknown[]) => unknown
84-
)(...args);
85-
}
86-
return undefined;
87-
};
88-
}
89-
if (prop === 'mockRestore') {
90-
return () => {
91-
(target as jest.SpyInstance).mockRestore();
92-
};
93-
}
68+
// Switch on the specific methods we need to implement
69+
switch (prop) {
70+
case 'mockImplementation':
71+
return (fn: () => void) => {
72+
(target as jest.SpyInstance).mockImplementation(fn);
73+
return createSmartSpy(target as jest.SpyInstance);
74+
};
75+
76+
case 'toHaveBeenCalledWith':
77+
return (...args: unknown[]) => {
78+
// Jest SpyInstance doesn't have toHaveBeenCalledWith as a method,
79+
// but Vitest spies do. For compatibility, we implement it by checking
80+
// the spy's call history manually to match Vitest's behavior.
81+
const jestSpy = target as jest.SpyInstance;
9482

95-
// For everything else, just pass through to the real spy
96-
return (target as Record<string | symbol, unknown>)[prop];
83+
// Check if any call matches the expected arguments
84+
const calls = jestSpy.mock.calls;
85+
const hasMatchingCall = calls.some((call: unknown[]) => {
86+
if (call.length !== args.length) return false;
87+
return call.every((arg: unknown, index: number) => {
88+
// Use Jest's deep equality matching
89+
try {
90+
expect(arg).toEqual(args[index]);
91+
return true;
92+
} catch {
93+
return false;
94+
}
95+
});
96+
});
97+
98+
if (!hasMatchingCall) {
99+
throw new Error(
100+
`Expected spy to have been called with [${args.map((arg: unknown) => JSON.stringify(arg)).join(', ')}], ` +
101+
`but it was called with: ${calls
102+
.map(
103+
(call: unknown[]) =>
104+
`[${call.map((arg: unknown) => JSON.stringify(arg)).join(', ')}]`
105+
)
106+
.join(', ')}`
107+
);
108+
}
109+
};
110+
111+
case 'mockRestore':
112+
return () => {
113+
(target as jest.SpyInstance).mockRestore();
114+
};
115+
116+
default:
117+
// For everything else, just pass through to the real spy
118+
return (target as Record<string | symbol, unknown>)[prop];
119+
}
97120
},
98121
}) as SmartSpy;
99122
}

0 commit comments

Comments
 (0)