Skip to content

Commit 30e8020

Browse files
authored
fix(mock): allow to mock methods in getters (#10156)
1 parent 194701f commit 30e8020

File tree

3 files changed

+72
-10
lines changed

3 files changed

+72
-10
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
### Fixes
88

9+
- `[jest-mock]` Allow to mock methods in getters (TypeScript 3.9 export)
10+
911
### Chore & Maintenance
1012

1113
### Performance

packages/jest-mock/src/__tests__/index.test.ts

+40
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,46 @@ describe('moduleMocker', () => {
11761176
expect(spy1.mock.calls.length).toBe(1);
11771177
expect(spy2.mock.calls.length).toBe(1);
11781178
});
1179+
1180+
it('should work with getters', () => {
1181+
let isOriginalCalled = false;
1182+
let originalCallThis;
1183+
let originalCallArguments;
1184+
const obj = {
1185+
get method() {
1186+
return function () {
1187+
isOriginalCalled = true;
1188+
originalCallThis = this;
1189+
originalCallArguments = arguments;
1190+
};
1191+
},
1192+
};
1193+
1194+
const spy = moduleMocker.spyOn(obj, 'method');
1195+
1196+
const thisArg = {this: true};
1197+
const firstArg = {first: true};
1198+
const secondArg = {second: true};
1199+
obj.method.call(thisArg, firstArg, secondArg);
1200+
expect(isOriginalCalled).toBe(true);
1201+
expect(originalCallThis).toBe(thisArg);
1202+
expect(originalCallArguments.length).toBe(2);
1203+
expect(originalCallArguments[0]).toBe(firstArg);
1204+
expect(originalCallArguments[1]).toBe(secondArg);
1205+
expect(spy).toHaveBeenCalled();
1206+
1207+
isOriginalCalled = false;
1208+
originalCallThis = null;
1209+
originalCallArguments = null;
1210+
spy.mockRestore();
1211+
obj.method.call(thisArg, firstArg, secondArg);
1212+
expect(isOriginalCalled).toBe(true);
1213+
expect(originalCallThis).toBe(thisArg);
1214+
expect(originalCallArguments.length).toBe(2);
1215+
expect(originalCallArguments[0]).toBe(firstArg);
1216+
expect(originalCallArguments[1]).toBe(secondArg);
1217+
expect(spy).not.toHaveBeenCalled();
1218+
});
11791219
});
11801220

11811221
describe('spyOnProperty', () => {

packages/jest-mock/src/index.ts

+30-10
Original file line numberDiff line numberDiff line change
@@ -985,17 +985,37 @@ class ModuleMockerClass {
985985

986986
const isMethodOwner = object.hasOwnProperty(methodName);
987987

988-
// @ts-expect-error overriding original method with a Mock
989-
object[methodName] = this._makeComponent({type: 'function'}, () => {
990-
if (isMethodOwner) {
991-
object[methodName] = original;
992-
} else {
993-
delete object[methodName];
994-
}
995-
});
988+
let descriptor = Object.getOwnPropertyDescriptor(object, methodName);
989+
let proto = Object.getPrototypeOf(object);
990+
991+
while (!descriptor && proto !== null) {
992+
descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
993+
proto = Object.getPrototypeOf(proto);
994+
}
995+
996+
let mock: JestMock.Mock<unknown, Array<unknown>>;
997+
998+
if (descriptor && descriptor.get) {
999+
const originalGet = descriptor.get;
1000+
mock = this._makeComponent({type: 'function'}, () => {
1001+
descriptor!.get = originalGet;
1002+
Object.defineProperty(object, methodName, descriptor!);
1003+
});
1004+
descriptor.get = () => mock;
1005+
Object.defineProperty(object, methodName, descriptor);
1006+
} else {
1007+
mock = this._makeComponent({type: 'function'}, () => {
1008+
if (isMethodOwner) {
1009+
object[methodName] = original;
1010+
} else {
1011+
delete object[methodName];
1012+
}
1013+
});
1014+
// @ts-expect-error overriding original method with a Mock
1015+
object[methodName] = mock;
1016+
}
9961017

997-
// @ts-expect-error original method is now a Mock
998-
object[methodName].mockImplementation(function (this: unknown) {
1018+
mock.mockImplementation(function (this: unknown) {
9991019
return original.apply(this, arguments);
10001020
});
10011021
}

0 commit comments

Comments
 (0)