Skip to content

Commit 188da1e

Browse files
fix: called spied on functions with 'this' (#463)
1 parent ff97b3c commit 188da1e

File tree

2 files changed

+40
-4
lines changed

2 files changed

+40
-4
lines changed

packages/helpers/lib/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,39 @@ export class RandomMocker {
3636
}
3737
}
3838

39+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
40+
function assertIsFunction(func: unknown): asserts func is Function {
41+
if (!(typeof func === "function")) {
42+
throw new TypeError("spyOn can only be called on function properties");
43+
}
44+
}
45+
3946
/** Calling spyOn creates a simple spy, inspired by Jest and Jasmine's spyOn functions.
4047
* @param obj - The object to spy on
4148
* @param method - The method to spy on
4249
* @returns A spy function that can be used to track calls to the original method
4350
*/
4451
export function spyOn<Args extends unknown[], Return>(
45-
obj: Record<string, (...args: Args) => Return>,
52+
obj: Record<string, unknown>,
4653
method: string,
4754
): {
4855
restore: () => void;
4956
calls: Args[];
5057
returns: Return[];
5158
} {
5259
const original = obj[method];
60+
assertIsFunction(original);
61+
5362
const calls: Args[] = [];
5463
const results: Return[] = [];
5564

56-
const fn = (...args: Args) => {
65+
function fn(this: unknown, ...args: Args): unknown {
5766
calls.push(args);
58-
const result = original(...args);
67+
// If fn is called, original will be a function.
68+
const result = (original as (...x: Args) => Return).apply(this, args);
5969
results.push(result);
6070
return result;
61-
};
71+
}
6272

6373
obj[method] = fn;
6474

packages/tests/curriculum-helper.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,32 @@ describe("spyOn", () => {
132132
obj.method("arg");
133133
expect(spy.calls).toEqual([]);
134134
});
135+
136+
it("should call the original function, providing 'this'", () => {
137+
const obj = {
138+
value: "obj",
139+
method(arg = "") {
140+
return this.value + arg;
141+
},
142+
};
143+
144+
const spy = helper.spyOn(obj, "method");
145+
const result = obj.method("arg");
146+
expect(result).toBe("objarg");
147+
expect(spy.calls).toEqual([["arg"]]);
148+
});
149+
150+
it("should throw if the spied on property is not a function", () => {
151+
const obj = {
152+
notAFunction: "foo",
153+
};
154+
155+
vitest.spyOn(console, "warn").mockImplementation(() => {});
156+
157+
expect(() => helper.spyOn(obj, "notAFunction")).toThrow(
158+
"spyOn can only be called on function properties",
159+
);
160+
});
135161
});
136162

137163
describe("spyOnCallbacks", () => {

0 commit comments

Comments
 (0)