diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f6aad60dce..b9f59f45e805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ([#5670](https://github.com/facebook/jest/pull/5670)) * `[expect]` Add inverse matchers (`expect.not.arrayContaining`, etc., [#5517](https://github.com/facebook/jest/pull/5517)) +* `[jest-mock]` Update `spyOnProperty` to support spying on the prototype chain + ([#5753](https://github.com/facebook/jest/pull/5753)) * `[jest-mock]` Add tracking of return values in the `mock` property ([#5752](https://github.com/facebook/jest/pull/5752)) * `[jest-mock]` Add tracking of thrown errors in the `mock` property diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index 58ca77cf74a4..7c3f983aa027 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -900,5 +900,112 @@ describe('moduleMocker', () => { expect(spy1.mock.calls.length).toBe(1); expect(spy2.mock.calls.length).toBe(1); }); + + it('should work with getters on the prototype chain', () => { + let isOriginalCalled = false; + let originalCallThis; + let originalCallArguments; + const prototype = { + get method() { + return function() { + isOriginalCalled = true; + originalCallThis = this; + originalCallArguments = arguments; + }; + }, + }; + const obj = Object.create(prototype, {}); + + const spy = moduleMocker.spyOn(obj, 'method', 'get'); + + const thisArg = {this: true}; + const firstArg = {first: true}; + const secondArg = {second: true}; + obj.method.call(thisArg, firstArg, secondArg); + expect(isOriginalCalled).toBe(true); + expect(originalCallThis).toBe(thisArg); + expect(originalCallArguments.length).toBe(2); + expect(originalCallArguments[0]).toBe(firstArg); + expect(originalCallArguments[1]).toBe(secondArg); + expect(spy).toHaveBeenCalled(); + + isOriginalCalled = false; + originalCallThis = null; + originalCallArguments = null; + spy.mockReset(); + spy.mockRestore(); + obj.method.call(thisArg, firstArg, secondArg); + expect(isOriginalCalled).toBe(true); + expect(originalCallThis).toBe(thisArg); + expect(originalCallArguments.length).toBe(2); + expect(originalCallArguments[0]).toBe(firstArg); + expect(originalCallArguments[1]).toBe(secondArg); + expect(spy).not.toHaveBeenCalled(); + }); + + test('should work with setters on the prototype chain', () => { + const prototype = { + _property: false, + set property(value) { + this._property = value; + }, + get property() { + return this._property; + }, + }; + const obj = Object.create(prototype, {}); + + const spy = moduleMocker.spyOn(obj, 'property', 'set'); + obj.property = true; + expect(spy).toHaveBeenCalled(); + expect(obj.property).toBe(true); + obj.property = false; + spy.mockReset(); + spy.mockRestore(); + obj.property = true; + expect(spy).not.toHaveBeenCalled(); + expect(obj.property).toBe(true); + }); + + it('supports restoring all spies on the prototype chain', () => { + let methodOneCalls = 0; + let methodTwoCalls = 0; + const prototype = { + get methodOne() { + return function() { + methodOneCalls++; + }; + }, + get methodTwo() { + return function() { + methodTwoCalls++; + }; + }, + }; + const obj = Object.create(prototype, {}); + + const spy1 = moduleMocker.spyOn(obj, 'methodOne', 'get'); + const spy2 = moduleMocker.spyOn(obj, 'methodTwo', 'get'); + + // First, we call with the spies: both spies and both original functions + // should be called. + obj.methodOne(); + obj.methodTwo(); + expect(methodOneCalls).toBe(1); + expect(methodTwoCalls).toBe(1); + expect(spy1.mock.calls.length).toBe(1); + expect(spy2.mock.calls.length).toBe(1); + + moduleMocker.restoreAllMocks(); + + // Then, after resetting all mocks, we call methods again. Only the real + // methods should bump their count, not the spies. + obj.methodOne(); + obj.methodTwo(); + expect(methodOneCalls).toBe(2); + expect(methodTwoCalls).toBe(2); + expect(spy1.mock.calls.length).toBe(1); + expect(spy2.mock.calls.length).toBe(1); + }); }); }); diff --git a/packages/jest-mock/src/index.js b/packages/jest-mock/src/index.js index e6bed9b123e1..ff02d3a5ff38 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -757,7 +757,13 @@ class ModuleMockerClass { throw new Error('No property name supplied'); } - const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); + let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); + let proto = Object.getPrototypeOf(obj); + + while (!descriptor && proto !== null) { + descriptor = Object.getOwnPropertyDescriptor(proto, propertyName); + proto = Object.getPrototypeOf(proto); + } if (!descriptor) { throw new Error(propertyName + ' property does not exist');