From 1a3b82a949776971075efeb08210e1bd464a11de Mon Sep 17 00:00:00 2001 From: phra Date: Mon, 18 Dec 2017 14:35:40 +0100 Subject: [PATCH 01/18] feat: implement spyOnProperty method, fixes #5106 --- flow-typed/npm/jest_v21.x.x.js | 1 + packages/jest-jasmine2/src/index.js | 3 +- packages/jest-jasmine2/src/jasmine/Env.js | 12 ++-- .../src/jasmine/jasmine_light.js | 4 ++ .../jest-jasmine2/src/jasmine/spy_registry.js | 63 +++++++++++++++++ packages/jest-mock/src/index.js | 67 +++++++++++++++++-- packages/jest-runtime/src/index.js | 32 ++++----- types/Jest.js | 1 + 8 files changed, 159 insertions(+), 24 deletions(-) diff --git a/flow-typed/npm/jest_v21.x.x.js b/flow-typed/npm/jest_v21.x.x.js index 4a467880c095..8dc63523cd46 100644 --- a/flow-typed/npm/jest_v21.x.x.js +++ b/flow-typed/npm/jest_v21.x.x.js @@ -555,6 +555,7 @@ declare var expect: { // TODO handle return type // http://jasmine.github.io/2.4/introduction.html#section-Spies declare function spyOn(value: mixed, method: string): Object; +declare function spyOnProperty(value: mixed, propertyName: string, accessType: 'get' | 'set'): Object; /** Holds all functions related to manipulating test runner */ declare var jest: JestObjectType; diff --git a/packages/jest-jasmine2/src/index.js b/packages/jest-jasmine2/src/index.js index 8dc13639b70e..b8bb08448b96 100644 --- a/packages/jest-jasmine2/src/index.js +++ b/packages/jest-jasmine2/src/index.js @@ -161,9 +161,10 @@ const addSnapshotData = (results, snapshotState) => { }); const uncheckedCount = snapshotState.getUncheckedCount(); - const uncheckedKeys = snapshotState.getUncheckedKeys(); + let uncheckedKeys if (uncheckedCount) { + uncheckedKeys = snapshotState.getUncheckedKeys(); snapshotState.removeUncheckedKeys(); } diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js index c0cc29f7f7cc..b542262f193f 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -285,6 +285,10 @@ export default function(j$) { return spyRegistry.spyOn.apply(spyRegistry, arguments); }; + this.spyOnProperty = function() { + return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); + }; + const suiteFactory = function(description) { const suite = new j$.Suite({ id: getNextSuiteId(), @@ -434,10 +438,10 @@ export default function(j$) { if (currentSpec !== null) { throw new Error( 'Tests cannot be nested. Test `' + - spec.description + - '` cannot run because it is nested within `' + - currentSpec.description + - '`.', + spec.description + + '` cannot run because it is nested within `' + + currentSpec.description + + '`.', ); } currentDeclarationSuite.addChild(spec); diff --git a/packages/jest-jasmine2/src/jasmine/jasmine_light.js b/packages/jest-jasmine2/src/jasmine/jasmine_light.js index 68f4ac1122dd..b9ebb0c1b28f 100644 --- a/packages/jest-jasmine2/src/jasmine/jasmine_light.js +++ b/packages/jest-jasmine2/src/jasmine/jasmine_light.js @@ -120,6 +120,10 @@ exports.interface = function(jasmine: Jasmine, env: any) { return env.spyOn(obj, methodName); }, + spyOnProperty: function (obj: Object, methodName: string, accessType = 'get') { + return env.spyOnProperty(obj, methodName, accessType); + }, + jsApiReporter: new jasmine.JsApiReporter({ timer: new jasmine.Timer(), }), diff --git a/packages/jest-jasmine2/src/jasmine/spy_registry.js b/packages/jest-jasmine2/src/jasmine/spy_registry.js index 31d156a7fadb..74ee51265c13 100644 --- a/packages/jest-jasmine2/src/jasmine/spy_registry.js +++ b/packages/jest-jasmine2/src/jasmine/spy_registry.js @@ -129,6 +129,69 @@ export default function SpyRegistry(options: Object) { return spiedMethod; }; + this.spyOnProperty = function(obj, propertyName, accessType = 'get') { + if (!obj) { + throw new Error('spyOn could not find an object to spy upon for ' + propertyName + ''); + } + + if (!propertyName) { + throw new Error('No property name supplied'); + } + + let descriptor; + try { + descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); + } catch (e) { + // IE 8 doesn't support `definePropery` on non-DOM nodes + } + + if (!descriptor) { + throw new Error(propertyName + ' property does not exist'); + } + + if (!descriptor.configurable) { + throw new Error(propertyName + ' is not declared configurable'); + } + + if (!descriptor[accessType]) { + throw new Error('Property ' + propertyName + ' does not have access type ' + accessType); + } + + if (obj[propertyName] && isSpy(obj[propertyName])) { + if (this.respy) { + return obj[propertyName]; + } else { + throw new Error( + getErrorMsg(propertyName + ' has already been spied upon'), + ); + } + } + + const originalDescriptor = descriptor; + const spiedProperty = createSpy(propertyName, descriptor[accessType]); + let restoreStrategy; + + if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { + restoreStrategy = function () { + Object.defineProperty(obj, propertyName, originalDescriptor); + }; + } else { + restoreStrategy = function () { + delete obj[propertyName]; + }; + } + + currentSpies().push({ + restoreObjectToOriginalState: restoreStrategy + }); + + const spiedDescriptor = Object.assign({}, descriptor, { [accessType]: spiedProperty }) + + Object.defineProperty(obj, propertyName, spiedDescriptor); + + return spiedProperty; + }; + this.clearSpies = function() { const spies = currentSpies(); for (let i = spies.length - 1; i >= 0; i--) { diff --git a/packages/jest-mock/src/index.js b/packages/jest-mock/src/index.js index 33019e3edb51..b5f6ac95f0af 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -672,10 +672,10 @@ class ModuleMockerClass { if (typeof original !== 'function') { throw new Error( 'Cannot spy the ' + - methodName + - ' property because it is not a function; ' + - this._typeOf(original) + - ' given instead', + methodName + + ' property because it is not a function; ' + + this._typeOf(original) + + ' given instead', ); } @@ -691,6 +691,65 @@ class ModuleMockerClass { return object[methodName]; } + spyOnProperty(object: any, propertyName: any, accessType = 'get'): any { + if (typeof object !== 'object' && typeof object !== 'function') { + throw new Error( + 'Cannot spyOn on a primitive value; ' + this._typeOf(object) + ' given', + ); + } + + if (!obj) { + throw new Error('spyOn could not find an object to spy upon for ' + propertyName + ''); + } + + if (!propertyName) { + throw new Error('No property name supplied'); + } + + let descriptor; + try { + descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); + } catch (e) { + // IE 8 doesn't support `definePropery` on non-DOM nodes + } + + if (!descriptor) { + throw new Error(propertyName + ' property does not exist'); + } + + if (!descriptor.configurable) { + throw new Error(propertyName + ' is not declared configurable'); + } + + if (!descriptor[accessType]) { + throw new Error('Property ' + propertyName + ' does not have access type ' + accessType); + } + + const original = descriptor[accessType] + + if (!this.isMockFunction(original)) { + if (typeof original !== 'function') { + throw new Error( + 'Cannot spy the ' + + methodName + + ' property because it is not a function; ' + + this._typeOf(original) + + ' given instead', + ); + } + + descriptor[accessType] = this._makeComponent({ type: 'function' }, () => { + descriptor[accessType] = original; + }); + + descriptor[accessType].mockImplementation(function () { + return original.apply(this, arguments); + }); + } + + return descriptor[accessType]; + } + clearAllMocks() { this._mockState = new WeakMap(); } diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 9a322f6fc32d..01c3e86a675b 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -30,21 +30,21 @@ import {options as cliOptions} from './cli/args'; type Module = {| children: Array, - exports: any, - filename: string, - id: string, - loaded: boolean, - parent?: Module, - paths?: Array, - require?: (id: string) => any, + exports: any, + filename: string, + id: string, + loaded: boolean, + parent?: Module, + paths?: Array, + require?: (id: string) => any, |}; type HasteMapOptions = {| console?: Console, - maxWorkers: number, - resetCache: boolean, - watch?: boolean, - watchman: boolean, + maxWorkers: number, + resetCache: boolean, + watch?: boolean, + watchman: boolean, |}; type InternalModuleOptions = {| @@ -550,7 +550,7 @@ class Runtime { filename, // $FlowFixMe (localModule.require: LocalModuleRequire), - ), // jest object + ), // jest object ); this._isCurrentlyExecutingManualMock = origCurrExecutingManualMock; @@ -595,7 +595,7 @@ class Runtime { if (mockMetadata == null) { throw new Error( `Failed to get mock metadata: ${modulePath}\n\n` + - `See: http://facebook.github.io/jest/docs/manual-mocks.html#content`, + `See: http://facebook.github.io/jest/docs/manual-mocks.html#content`, ); } this._mockMetaDataCache[modulePath] = mockMetadata; @@ -763,13 +763,14 @@ class Runtime { }; const fn = this._moduleMocker.fn.bind(this._moduleMocker); const spyOn = this._moduleMocker.spyOn.bind(this._moduleMocker); + const spyOnProperty = this._moduleMocker.spyOnProperty.bind(this._moduleMocker); const setTimeout = (timeout: number) => { this._environment.global.jasmine ? (this._environment.global.jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout) : (this._environment.global[ - Symbol.for('TEST_TIMEOUT_SYMBOL') - ] = timeout); + Symbol.for('TEST_TIMEOUT_SYMBOL') + ] = timeout); return jestObject; }; @@ -811,6 +812,7 @@ class Runtime { setMockFactory(moduleName, () => mock), setTimeout, spyOn, + spyOnProperty, unmock, useFakeTimers, useRealTimers, diff --git a/types/Jest.js b/types/Jest.js index 3b04e043e361..6007812c1dc3 100644 --- a/types/Jest.js +++ b/types/Jest.js @@ -44,6 +44,7 @@ export type Jest = {| setMock(moduleName: string, moduleExports: any): Jest, setTimeout(timeout: number): Jest, spyOn(object: Object, methodName: string): JestMockFn, + spyOnProperty(object: Object, methodName: string, accessType: string): JestMockFn, unmock(moduleName: string): Jest, useFakeTimers(): Jest, useRealTimers(): Jest, From 4cfb737950a6e0bf04eab24c149115944fc4dc7f Mon Sep 17 00:00:00 2001 From: phra Date: Mon, 18 Dec 2017 15:12:12 +0100 Subject: [PATCH 02/18] style: fix indentation of mock, runtime, jasmine2 --- packages/jest-jasmine2/src/jasmine/Env.js | 8 +++--- packages/jest-mock/src/index.js | 16 ++++++------ packages/jest-runtime/src/index.js | 30 +++++++++++------------ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js index b542262f193f..5c6b671f96db 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -438,10 +438,10 @@ export default function(j$) { if (currentSpec !== null) { throw new Error( 'Tests cannot be nested. Test `' + - spec.description + - '` cannot run because it is nested within `' + - currentSpec.description + - '`.', + spec.description + + '` cannot run because it is nested within `' + + currentSpec.description + + '`.', ); } currentDeclarationSuite.addChild(spec); diff --git a/packages/jest-mock/src/index.js b/packages/jest-mock/src/index.js index b5f6ac95f0af..4e490bc9b216 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -672,10 +672,10 @@ class ModuleMockerClass { if (typeof original !== 'function') { throw new Error( 'Cannot spy the ' + - methodName + - ' property because it is not a function; ' + - this._typeOf(original) + - ' given instead', + methodName + + ' property because it is not a function; ' + + this._typeOf(original) + + ' given instead', ); } @@ -731,10 +731,10 @@ class ModuleMockerClass { if (typeof original !== 'function') { throw new Error( 'Cannot spy the ' + - methodName + - ' property because it is not a function; ' + - this._typeOf(original) + - ' given instead', + methodName + + ' property because it is not a function; ' + + this._typeOf(original) + + ' given instead', ); } diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 01c3e86a675b..38cc5cd7f43a 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -30,21 +30,21 @@ import {options as cliOptions} from './cli/args'; type Module = {| children: Array, - exports: any, - filename: string, - id: string, - loaded: boolean, - parent?: Module, - paths?: Array, - require?: (id: string) => any, + exports: any, + filename: string, + id: string, + loaded: boolean, + parent?: Module, + paths?: Array, + require?: (id: string) => any, |}; type HasteMapOptions = {| console?: Console, - maxWorkers: number, - resetCache: boolean, - watch?: boolean, - watchman: boolean, + maxWorkers: number, + resetCache: boolean, + watch?: boolean, + watchman: boolean, |}; type InternalModuleOptions = {| @@ -550,7 +550,7 @@ class Runtime { filename, // $FlowFixMe (localModule.require: LocalModuleRequire), - ), // jest object + ), // jest object ); this._isCurrentlyExecutingManualMock = origCurrExecutingManualMock; @@ -595,7 +595,7 @@ class Runtime { if (mockMetadata == null) { throw new Error( `Failed to get mock metadata: ${modulePath}\n\n` + - `See: http://facebook.github.io/jest/docs/manual-mocks.html#content`, + `See: http://facebook.github.io/jest/docs/manual-mocks.html#content`, ); } this._mockMetaDataCache[modulePath] = mockMetadata; @@ -769,8 +769,8 @@ class Runtime { this._environment.global.jasmine ? (this._environment.global.jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout) : (this._environment.global[ - Symbol.for('TEST_TIMEOUT_SYMBOL') - ] = timeout); + Symbol.for('TEST_TIMEOUT_SYMBOL') + ] = timeout); return jestObject; }; From 65d884d49b450e4dabf959577ed60f2ed751ffeb Mon Sep 17 00:00:00 2001 From: phra Date: Mon, 18 Dec 2017 21:24:50 +0100 Subject: [PATCH 03/18] test: fix failing tests for spyOnProperty --- flow-typed/npm/jest_v21.x.x.js | 2 +- packages/jest-jasmine2/src/index.js | 2 +- .../src/jasmine/jasmine_light.js | 2 +- .../jest-jasmine2/src/jasmine/spy_registry.js | 18 ++++++++---- packages/jest-mock/src/index.js | 29 +++++++++---------- packages/jest-runtime/src/index.js | 4 ++- types/Jest.js | 6 +++- 7 files changed, 37 insertions(+), 26 deletions(-) diff --git a/flow-typed/npm/jest_v21.x.x.js b/flow-typed/npm/jest_v21.x.x.js index 8dc63523cd46..ceb0799324c1 100644 --- a/flow-typed/npm/jest_v21.x.x.js +++ b/flow-typed/npm/jest_v21.x.x.js @@ -555,7 +555,7 @@ declare var expect: { // TODO handle return type // http://jasmine.github.io/2.4/introduction.html#section-Spies declare function spyOn(value: mixed, method: string): Object; -declare function spyOnProperty(value: mixed, propertyName: string, accessType: 'get' | 'set'): Object; +declare function spyOnProperty(value: mixed, propertyName: string, accessType: string): Object; /** Holds all functions related to manipulating test runner */ declare var jest: JestObjectType; diff --git a/packages/jest-jasmine2/src/index.js b/packages/jest-jasmine2/src/index.js index b8bb08448b96..630055ada8ed 100644 --- a/packages/jest-jasmine2/src/index.js +++ b/packages/jest-jasmine2/src/index.js @@ -161,7 +161,7 @@ const addSnapshotData = (results, snapshotState) => { }); const uncheckedCount = snapshotState.getUncheckedCount(); - let uncheckedKeys + let uncheckedKeys; if (uncheckedCount) { uncheckedKeys = snapshotState.getUncheckedKeys(); diff --git a/packages/jest-jasmine2/src/jasmine/jasmine_light.js b/packages/jest-jasmine2/src/jasmine/jasmine_light.js index b9ebb0c1b28f..81a397708865 100644 --- a/packages/jest-jasmine2/src/jasmine/jasmine_light.js +++ b/packages/jest-jasmine2/src/jasmine/jasmine_light.js @@ -120,7 +120,7 @@ exports.interface = function(jasmine: Jasmine, env: any) { return env.spyOn(obj, methodName); }, - spyOnProperty: function (obj: Object, methodName: string, accessType = 'get') { + spyOnProperty(obj: Object, methodName: string, accessType = 'get') { return env.spyOnProperty(obj, methodName, accessType); }, diff --git a/packages/jest-jasmine2/src/jasmine/spy_registry.js b/packages/jest-jasmine2/src/jasmine/spy_registry.js index 74ee51265c13..b1a3ce600edb 100644 --- a/packages/jest-jasmine2/src/jasmine/spy_registry.js +++ b/packages/jest-jasmine2/src/jasmine/spy_registry.js @@ -131,7 +131,9 @@ export default function SpyRegistry(options: Object) { this.spyOnProperty = function(obj, propertyName, accessType = 'get') { if (!obj) { - throw new Error('spyOn could not find an object to spy upon for ' + propertyName + ''); + throw new Error( + 'spyOn could not find an object to spy upon for ' + propertyName + '', + ); } if (!propertyName) { @@ -154,7 +156,9 @@ export default function SpyRegistry(options: Object) { } if (!descriptor[accessType]) { - throw new Error('Property ' + propertyName + ' does not have access type ' + accessType); + throw new Error( + 'Property ' + propertyName + ' does not have access type ' + accessType, + ); } if (obj[propertyName] && isSpy(obj[propertyName])) { @@ -172,20 +176,22 @@ export default function SpyRegistry(options: Object) { let restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { - restoreStrategy = function () { + restoreStrategy = function() { Object.defineProperty(obj, propertyName, originalDescriptor); }; } else { - restoreStrategy = function () { + restoreStrategy = function() { delete obj[propertyName]; }; } currentSpies().push({ - restoreObjectToOriginalState: restoreStrategy + restoreObjectToOriginalState: restoreStrategy, }); - const spiedDescriptor = Object.assign({}, descriptor, { [accessType]: spiedProperty }) + const spiedDescriptor = Object.assign({}, descriptor, { + [accessType]: spiedProperty, + }); Object.defineProperty(obj, propertyName, spiedDescriptor); diff --git a/packages/jest-mock/src/index.js b/packages/jest-mock/src/index.js index 4e490bc9b216..cc0f3d5a854e 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -691,27 +691,24 @@ class ModuleMockerClass { return object[methodName]; } - spyOnProperty(object: any, propertyName: any, accessType = 'get'): any { - if (typeof object !== 'object' && typeof object !== 'function') { + spyOnProperty(obj: any, propertyName: any, accessType: string = 'get'): any { + if (typeof obj !== 'object' && typeof obj !== 'function') { throw new Error( - 'Cannot spyOn on a primitive value; ' + this._typeOf(object) + ' given', + 'Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given', ); } if (!obj) { - throw new Error('spyOn could not find an object to spy upon for ' + propertyName + ''); + throw new Error( + 'spyOn could not find an object to spy upon for ' + propertyName + '', + ); } if (!propertyName) { throw new Error('No property name supplied'); } - let descriptor; - try { - descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); - } catch (e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } + const descriptor = Object.getOwnPropertyDescriptor(obj, propertyName); if (!descriptor) { throw new Error(propertyName + ' property does not exist'); @@ -722,27 +719,29 @@ class ModuleMockerClass { } if (!descriptor[accessType]) { - throw new Error('Property ' + propertyName + ' does not have access type ' + accessType); + throw new Error( + 'Property ' + propertyName + ' does not have access type ' + accessType, + ); } - const original = descriptor[accessType] + const original = descriptor[accessType]; if (!this.isMockFunction(original)) { if (typeof original !== 'function') { throw new Error( 'Cannot spy the ' + - methodName + + propertyName + ' property because it is not a function; ' + this._typeOf(original) + ' given instead', ); } - descriptor[accessType] = this._makeComponent({ type: 'function' }, () => { + descriptor[accessType] = this._makeComponent({type: 'function'}, () => { descriptor[accessType] = original; }); - descriptor[accessType].mockImplementation(function () { + descriptor[accessType].mockImplementation(function() { return original.apply(this, arguments); }); } diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 38cc5cd7f43a..8058d9fa0439 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -763,7 +763,9 @@ class Runtime { }; const fn = this._moduleMocker.fn.bind(this._moduleMocker); const spyOn = this._moduleMocker.spyOn.bind(this._moduleMocker); - const spyOnProperty = this._moduleMocker.spyOnProperty.bind(this._moduleMocker); + const spyOnProperty = this._moduleMocker.spyOnProperty.bind( + this._moduleMocker, + ); const setTimeout = (timeout: number) => { this._environment.global.jasmine diff --git a/types/Jest.js b/types/Jest.js index 6007812c1dc3..f22bc79ec167 100644 --- a/types/Jest.js +++ b/types/Jest.js @@ -44,7 +44,11 @@ export type Jest = {| setMock(moduleName: string, moduleExports: any): Jest, setTimeout(timeout: number): Jest, spyOn(object: Object, methodName: string): JestMockFn, - spyOnProperty(object: Object, methodName: string, accessType: string): JestMockFn, + spyOnProperty( + object: Object, + methodName: string, + accessType: string, + ): JestMockFn, unmock(moduleName: string): Jest, useFakeTimers(): Jest, useRealTimers(): Jest, From a4a63132213f6358bd0233cc49164226c01a1a7f Mon Sep 17 00:00:00 2001 From: phra Date: Mon, 25 Dec 2017 21:56:44 +0100 Subject: [PATCH 04/18] test: implement tests for #1214 --- .../jest-mock/src/__tests__/jest_mock.test.js | 92 +++++++++++++++++++ packages/jest-mock/src/index.js | 2 + .../src/__tests__/runtime_jest_spy_on.test.js | 21 +++++ 3 files changed, 115 insertions(+) diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index e081c8ecbc7e..3be5440a597f 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -666,4 +666,96 @@ describe('moduleMocker', () => { expect(spy2.mock.calls.length).toBe(1); }); }); + + describe('spyOnProperty', () => { + it('should work', () => { + let isOriginalCalled = false; + let originalCallThis; + let originalCallArguments; + const obj = { + get method() { + return function () { + isOriginalCalled = true; + originalCallThis = this; + originalCallArguments = arguments; + } + }, + }; + + const spy = moduleMocker.spyOnProperty(obj, 'method'); + + 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(); + console.log('porcoddddddiooo', obj, obj.method) + 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(); + }); + + it('should throw on invalid input', () => { + expect(() => { + moduleMocker.spyOnProperty(null, 'method'); + }).toThrow(); + expect(() => { + moduleMocker.spyOnProperty({}, 'method'); + }).toThrow(); + expect(() => { + moduleMocker.spyOnProperty({method: 10}, 'method'); + }).toThrow(); + }); + + it('supports restoring all spies', () => { + let methodOneCalls = 0; + let methodTwoCalls = 0; + const obj = { + get methodOne() { + return function () {methodOneCalls++}; + }, + get methodTwo() { + return function () {methodTwoCalls++}; + }, + }; + + const spy1 = moduleMocker.spyOnProperty(obj, 'methodOne'); + const spy2 = moduleMocker.spyOnProperty(obj, 'methodTwo'); + + // 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 cc0f3d5a854e..2d6a6e2f1ce0 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -739,6 +739,7 @@ class ModuleMockerClass { descriptor[accessType] = this._makeComponent({type: 'function'}, () => { descriptor[accessType] = original; + Object.defineProperty(obj, propertyName, descriptor) }); descriptor[accessType].mockImplementation(function() { @@ -746,6 +747,7 @@ class ModuleMockerClass { }); } + Object.defineProperty(obj, propertyName, descriptor) return descriptor[accessType]; } diff --git a/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js b/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js index 2511e16cf8dd..c640eb6914e4 100644 --- a/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js +++ b/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js @@ -35,4 +35,25 @@ describe('Runtime', () => { expect(spy).toHaveBeenCalled(); })); }); + + describe('jest.spyOnProperty', () => { + it('calls the original function', () => + createRuntime(__filename).then(runtime => { + const root = runtime.requireModule(runtime.__mockRootPath); + + let isOriginalCalled = false; + const obj = { + get method() { + return () => isOriginalCalled = true; + }, + }; + + const spy = root.jest.spyOnProperty(obj, 'method'); + + obj.method(); + + expect(isOriginalCalled).toBe(true); + expect(spy).toHaveBeenCalled(); + })); + }); }); From d26337c4df03de3f33715e1b4900f4b58aeeedcf Mon Sep 17 00:00:00 2001 From: phra Date: Mon, 25 Dec 2017 21:58:13 +0100 Subject: [PATCH 05/18] style: fix eslint errors --- packages/jest-mock/src/__tests__/jest_mock.test.js | 14 +++++++++----- packages/jest-mock/src/index.js | 4 ++-- .../src/__tests__/runtime_jest_spy_on.test.js | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index 3be5440a597f..5a712fc65dd7 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -674,11 +674,11 @@ describe('moduleMocker', () => { let originalCallArguments; const obj = { get method() { - return function () { + return function() { isOriginalCalled = true; originalCallThis = this; originalCallArguments = arguments; - } + }; }, }; @@ -700,7 +700,7 @@ describe('moduleMocker', () => { originalCallArguments = null; spy.mockReset(); spy.mockRestore(); - console.log('porcoddddddiooo', obj, obj.method) + console.log('porcoddddddiooo', obj, obj.method); obj.method.call(thisArg, firstArg, secondArg); expect(isOriginalCalled).toBe(true); expect(originalCallThis).toBe(thisArg); @@ -727,10 +727,14 @@ describe('moduleMocker', () => { let methodTwoCalls = 0; const obj = { get methodOne() { - return function () {methodOneCalls++}; + return function() { + methodOneCalls++; + }; }, get methodTwo() { - return function () {methodTwoCalls++}; + return function() { + methodTwoCalls++; + }; }, }; diff --git a/packages/jest-mock/src/index.js b/packages/jest-mock/src/index.js index 2d6a6e2f1ce0..c78b0d648232 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -739,7 +739,7 @@ class ModuleMockerClass { descriptor[accessType] = this._makeComponent({type: 'function'}, () => { descriptor[accessType] = original; - Object.defineProperty(obj, propertyName, descriptor) + Object.defineProperty(obj, propertyName, descriptor); }); descriptor[accessType].mockImplementation(function() { @@ -747,7 +747,7 @@ class ModuleMockerClass { }); } - Object.defineProperty(obj, propertyName, descriptor) + Object.defineProperty(obj, propertyName, descriptor); return descriptor[accessType]; } diff --git a/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js b/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js index c640eb6914e4..1dab6a19e590 100644 --- a/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js +++ b/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js @@ -44,7 +44,7 @@ describe('Runtime', () => { let isOriginalCalled = false; const obj = { get method() { - return () => isOriginalCalled = true; + return () => (isOriginalCalled = true); }, }; From 7c4844dc364f46562c72e40262f76f47f15de6c9 Mon Sep 17 00:00:00 2001 From: phra Date: Thu, 4 Jan 2018 20:19:12 +0100 Subject: [PATCH 06/18] refactor: proxy spyOnProperty call behind spyOn --- packages/jest-jasmine2/src/jasmine/Env.js | 4 ---- packages/jest-jasmine2/src/jasmine/jasmine_light.js | 8 ++------ packages/jest-jasmine2/src/jasmine/spy_registry.js | 8 ++++++-- packages/jest-mock/src/__tests__/jest_mock.test.js | 6 +++--- packages/jest-mock/src/index.js | 8 ++++++-- .../src/__tests__/runtime_jest_spy_on.test.js | 2 +- packages/jest-runtime/src/index.js | 4 ---- types/Jest.js | 7 +------ 8 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/jest-jasmine2/src/jasmine/Env.js b/packages/jest-jasmine2/src/jasmine/Env.js index 5c6b671f96db..c0cc29f7f7cc 100644 --- a/packages/jest-jasmine2/src/jasmine/Env.js +++ b/packages/jest-jasmine2/src/jasmine/Env.js @@ -285,10 +285,6 @@ export default function(j$) { return spyRegistry.spyOn.apply(spyRegistry, arguments); }; - this.spyOnProperty = function() { - return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); - }; - const suiteFactory = function(description) { const suite = new j$.Suite({ id: getNextSuiteId(), diff --git a/packages/jest-jasmine2/src/jasmine/jasmine_light.js b/packages/jest-jasmine2/src/jasmine/jasmine_light.js index 81a397708865..911377194e92 100644 --- a/packages/jest-jasmine2/src/jasmine/jasmine_light.js +++ b/packages/jest-jasmine2/src/jasmine/jasmine_light.js @@ -116,12 +116,8 @@ exports.interface = function(jasmine: Jasmine, env: any) { return env.fail.apply(env, arguments); }, - spyOn(obj: Object, methodName: string) { - return env.spyOn(obj, methodName); - }, - - spyOnProperty(obj: Object, methodName: string, accessType = 'get') { - return env.spyOnProperty(obj, methodName, accessType); + spyOn(obj: Object, methodName: string, accessType?: string) { + return env.spyOn(obj, methodName, accessType); }, jsApiReporter: new jasmine.JsApiReporter({ diff --git a/packages/jest-jasmine2/src/jasmine/spy_registry.js b/packages/jest-jasmine2/src/jasmine/spy_registry.js index b1a3ce600edb..ec29545bf165 100644 --- a/packages/jest-jasmine2/src/jasmine/spy_registry.js +++ b/packages/jest-jasmine2/src/jasmine/spy_registry.js @@ -64,7 +64,11 @@ export default function SpyRegistry(options: Object) { this.respy = allow; }; - this.spyOn = function(obj, methodName) { + this.spyOn = function(obj, methodName, accessType?: string) { + if (accessType) { + return this._spyOnProperty(obj, methodName, accessType) + } + if (obj === void 0) { throw new Error( getErrorMsg( @@ -129,7 +133,7 @@ export default function SpyRegistry(options: Object) { return spiedMethod; }; - this.spyOnProperty = function(obj, propertyName, accessType = 'get') { + this._spyOnProperty = function(obj, propertyName, accessType = 'get') { if (!obj) { throw new Error( 'spyOn could not find an object to spy upon for ' + propertyName + '', diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index 5a712fc65dd7..fb7c98b1420c 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -682,7 +682,7 @@ describe('moduleMocker', () => { }, }; - const spy = moduleMocker.spyOnProperty(obj, 'method'); + const spy = moduleMocker.spyOn(obj, 'method', 'get'); const thisArg = {this: true}; const firstArg = {first: true}; @@ -738,8 +738,8 @@ describe('moduleMocker', () => { }, }; - const spy1 = moduleMocker.spyOnProperty(obj, 'methodOne'); - const spy2 = moduleMocker.spyOnProperty(obj, 'methodTwo'); + 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. diff --git a/packages/jest-mock/src/index.js b/packages/jest-mock/src/index.js index c78b0d648232..5ebcbbc26c96 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -659,7 +659,11 @@ class ModuleMockerClass { return fn; } - spyOn(object: any, methodName: any): any { + spyOn(object: any, methodName: any, accessType?: string): any { + if (accessType) { + return this._spyOnProperty(object, methodName, accessType) + } + if (typeof object !== 'object' && typeof object !== 'function') { throw new Error( 'Cannot spyOn on a primitive value; ' + this._typeOf(object) + ' given', @@ -691,7 +695,7 @@ class ModuleMockerClass { return object[methodName]; } - spyOnProperty(obj: any, propertyName: any, accessType: string = 'get'): any { + _spyOnProperty(obj: any, propertyName: any, accessType: string = 'get'): any { if (typeof obj !== 'object' && typeof obj !== 'function') { throw new Error( 'Cannot spyOn on a primitive value; ' + this._typeOf(obj) + ' given', diff --git a/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js b/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js index 1dab6a19e590..3c062d1fffcf 100644 --- a/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js +++ b/packages/jest-runtime/src/__tests__/runtime_jest_spy_on.test.js @@ -48,7 +48,7 @@ describe('Runtime', () => { }, }; - const spy = root.jest.spyOnProperty(obj, 'method'); + const spy = root.jest.spyOn(obj, 'method', 'get'); obj.method(); diff --git a/packages/jest-runtime/src/index.js b/packages/jest-runtime/src/index.js index 8058d9fa0439..9a322f6fc32d 100644 --- a/packages/jest-runtime/src/index.js +++ b/packages/jest-runtime/src/index.js @@ -763,9 +763,6 @@ class Runtime { }; const fn = this._moduleMocker.fn.bind(this._moduleMocker); const spyOn = this._moduleMocker.spyOn.bind(this._moduleMocker); - const spyOnProperty = this._moduleMocker.spyOnProperty.bind( - this._moduleMocker, - ); const setTimeout = (timeout: number) => { this._environment.global.jasmine @@ -814,7 +811,6 @@ class Runtime { setMockFactory(moduleName, () => mock), setTimeout, spyOn, - spyOnProperty, unmock, useFakeTimers, useRealTimers, diff --git a/types/Jest.js b/types/Jest.js index f22bc79ec167..45a8f98af0c6 100644 --- a/types/Jest.js +++ b/types/Jest.js @@ -43,12 +43,7 @@ export type Jest = {| runTimersToTime(msToRun: number): void, setMock(moduleName: string, moduleExports: any): Jest, setTimeout(timeout: number): Jest, - spyOn(object: Object, methodName: string): JestMockFn, - spyOnProperty( - object: Object, - methodName: string, - accessType: string, - ): JestMockFn, + spyOn(object: Object, methodName: string, accessType?: string): JestMockFn, unmock(moduleName: string): Jest, useFakeTimers(): Jest, useRealTimers(): Jest, From afb0e9610c117f88e21b8e9a81f436d67a189de6 Mon Sep 17 00:00:00 2001 From: phra Date: Thu, 4 Jan 2018 20:24:16 +0100 Subject: [PATCH 07/18] refactor: remove useless console.log --- packages/jest-mock/src/__tests__/jest_mock.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index fb7c98b1420c..5a2609ee20b6 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -700,7 +700,6 @@ describe('moduleMocker', () => { originalCallArguments = null; spy.mockReset(); spy.mockRestore(); - console.log('porcoddddddiooo', obj, obj.method); obj.method.call(thisArg, firstArg, secondArg); expect(isOriginalCalled).toBe(true); expect(originalCallThis).toBe(thisArg); From 05a303d8896f8e25ed406ab0dc642f7046838ed0 Mon Sep 17 00:00:00 2001 From: phra Date: Thu, 4 Jan 2018 20:25:40 +0100 Subject: [PATCH 08/18] style: fix eslint errors --- packages/jest-jasmine2/src/jasmine/spy_registry.js | 2 +- packages/jest-mock/src/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-jasmine2/src/jasmine/spy_registry.js b/packages/jest-jasmine2/src/jasmine/spy_registry.js index ec29545bf165..5427e811d6a1 100644 --- a/packages/jest-jasmine2/src/jasmine/spy_registry.js +++ b/packages/jest-jasmine2/src/jasmine/spy_registry.js @@ -66,7 +66,7 @@ export default function SpyRegistry(options: Object) { this.spyOn = function(obj, methodName, accessType?: string) { if (accessType) { - return this._spyOnProperty(obj, methodName, accessType) + return this._spyOnProperty(obj, methodName, accessType); } if (obj === void 0) { diff --git a/packages/jest-mock/src/index.js b/packages/jest-mock/src/index.js index 5ebcbbc26c96..7490110d5de1 100644 --- a/packages/jest-mock/src/index.js +++ b/packages/jest-mock/src/index.js @@ -661,7 +661,7 @@ class ModuleMockerClass { spyOn(object: any, methodName: any, accessType?: string): any { if (accessType) { - return this._spyOnProperty(object, methodName, accessType) + return this._spyOnProperty(object, methodName, accessType); } if (typeof object !== 'object' && typeof object !== 'function') { From fc0de22abdec3168062ccddbb19df914e8e3b64b Mon Sep 17 00:00:00 2001 From: Francesco Soncina Date: Thu, 4 Jan 2018 23:49:29 +0100 Subject: [PATCH 09/18] types: remove declaration of spyOnProperty --- flow-typed/npm/jest_v21.x.x.js | 1 - 1 file changed, 1 deletion(-) diff --git a/flow-typed/npm/jest_v21.x.x.js b/flow-typed/npm/jest_v21.x.x.js index ceb0799324c1..4a467880c095 100644 --- a/flow-typed/npm/jest_v21.x.x.js +++ b/flow-typed/npm/jest_v21.x.x.js @@ -555,7 +555,6 @@ declare var expect: { // TODO handle return type // http://jasmine.github.io/2.4/introduction.html#section-Spies declare function spyOn(value: mixed, method: string): Object; -declare function spyOnProperty(value: mixed, propertyName: string, accessType: string): Object; /** Holds all functions related to manipulating test runner */ declare var jest: JestObjectType; From 0a5e0832651d008f72599de4a997b3b7e06a3730 Mon Sep 17 00:00:00 2001 From: phra Date: Sat, 6 Jan 2018 00:24:31 +0100 Subject: [PATCH 10/18] docs: add documentation for accessType argument of spyOn --- docs/JestObjectAPI.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index daaab8766cd8..74abe4221464 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -418,3 +418,37 @@ test('plays video', () => { spy.mockRestore(); }); ``` + +### `jest.spyOn(object, methodName, accessType?)` +##### available in Jest **x.x.x+** + +Since Jest x.x.x+, the `jest.spyOn` method takes an optional third argument that can be `'get'` or `'get'` in order to install a spy as a getter or a setter respectively. This is also needed when you need a spy an existing getter/setter method. + +Example: + +```js +const video = { + get play() { // it's a getter! + return true; + }, +}; + +module.exports = video; +``` + +Example test: + +```js +const video = require('./video'); + +test('plays video', () => { + const spy = jest.spyOn(video, 'play', 'get'); // we pass 'get' + const isPlaying = video.play(); + + expect(spy).toHaveBeenCalled(); + expect(isPlaying).toBe(true); + + spy.mockReset(); + spy.mockRestore(); +}); +``` From c0894d971da0870b941e5989292c5c17645dcd10 Mon Sep 17 00:00:00 2001 From: Francesco Soncina Date: Sat, 6 Jan 2018 03:04:06 +0100 Subject: [PATCH 11/18] docs: fix typo in spyOn docs --- docs/JestObjectAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index 74abe4221464..337fe5118298 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -443,7 +443,7 @@ const video = require('./video'); test('plays video', () => { const spy = jest.spyOn(video, 'play', 'get'); // we pass 'get' - const isPlaying = video.play(); + const isPlaying = video.play; expect(spy).toHaveBeenCalled(); expect(isPlaying).toBe(true); From be83146258839cd4c1da3530c7ecd79e29a5ce61 Mon Sep 17 00:00:00 2001 From: phra Date: Sat, 6 Jan 2018 03:40:48 +0100 Subject: [PATCH 12/18] test(spyOn): fix typo in should throw on invalid input --- packages/jest-mock/src/__tests__/jest_mock.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index 5a2609ee20b6..84590ed5be5e 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -711,13 +711,13 @@ describe('moduleMocker', () => { it('should throw on invalid input', () => { expect(() => { - moduleMocker.spyOnProperty(null, 'method'); + moduleMocker.spyOn(null, 'method'); }).toThrow(); expect(() => { - moduleMocker.spyOnProperty({}, 'method'); + moduleMocker.spyOn({}, 'method'); }).toThrow(); expect(() => { - moduleMocker.spyOnProperty({method: 10}, 'method'); + moduleMocker.spyOn({method: 10}, 'method'); }).toThrow(); }); From e5aa9a66947dba977724b336c0f4d0d7e484ed29 Mon Sep 17 00:00:00 2001 From: phra Date: Sat, 6 Jan 2018 04:15:11 +0100 Subject: [PATCH 13/18] test(spyOn): add tests for setters --- .../jest-mock/src/__tests__/jest_mock.test.js | 77 ++++++++++++------- 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index 84590ed5be5e..a5ef4ba767bf 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -21,7 +21,7 @@ describe('moduleMocker', () => { describe('getMetadata', () => { it('returns the function `name` property', () => { - function x() {} + function x() { } const metadata = moduleMocker.getMetadata(x); expect(x.name).toBe('x'); expect(metadata.name).toBe('x'); @@ -60,7 +60,7 @@ describe('moduleMocker', () => { describe('generateFromMetadata', () => { it('forwards the function name property', () => { - function foo() {} + function foo() { } const mock = moduleMocker.generateFromMetadata( moduleMocker.getMetadata(foo), ); @@ -69,8 +69,8 @@ describe('moduleMocker', () => { it('escapes illegal characters in function name property', () => { function getMockFnWithOriginalName(name) { - const fn = () => {}; - Object.defineProperty(fn, 'name', {value: name}); + const fn = () => { }; + Object.defineProperty(fn, 'name', { value: name }); return moduleMocker.generateFromMetadata(moduleMocker.getMetadata(fn)); } @@ -93,7 +93,7 @@ describe('moduleMocker', () => { }); it('special cases the mockConstructor name', () => { - function mockConstructor() {} + function mockConstructor() { } const mock = moduleMocker.generateFromMetadata( moduleMocker.getMetadata(mockConstructor), ); @@ -102,8 +102,8 @@ describe('moduleMocker', () => { }); it('wont interfere with previous mocks on a shared prototype', () => { - const ClassFoo = function() {}; - ClassFoo.prototype.x = () => {}; + const ClassFoo = function () { }; + ClassFoo.prototype.x = () => { }; const ClassFooMock = moduleMocker.generateFromMetadata( moduleMocker.getMetadata(ClassFoo), ); @@ -131,7 +131,7 @@ describe('moduleMocker', () => { }, }, nonEnumMethod: { - value: () => {}, + value: () => { }, }, }, ); @@ -166,7 +166,7 @@ describe('moduleMocker', () => { it('mocks ES2015 non-enumerable methods', () => { class ClassFoo { - foo() {} + foo() { } toString() { return 'Foo'; } @@ -190,7 +190,7 @@ describe('moduleMocker', () => { }); it('mocks methods that are bound multiple times', () => { - const func = function func() {}; + const func = function func() { }; const multipleBoundFunc = func.bind(null).bind(null); const multipleBoundFuncMock = moduleMocker.generateFromMetadata( @@ -202,7 +202,7 @@ describe('moduleMocker', () => { it('mocks methods that are bound after mocking', () => { const fooMock = moduleMocker.generateFromMetadata( - moduleMocker.getMetadata(() => {}), + moduleMocker.getMetadata(() => { }), ); const barMock = moduleMocker.generateFromMetadata( @@ -475,13 +475,13 @@ describe('moduleMocker', () => { it('should mock constructor', () => { const mock1 = jest.fn(); const mock2 = jest.fn(); - const Module = jest.fn(() => ({someFn: mock1})); - const testFn = function() { + const Module = jest.fn(() => ({ someFn: mock1 })); + const testFn = function () { const m = new Module(); m.someFn(); }; - Module.mockImplementationOnce(() => ({someFn: mock2})); + Module.mockImplementationOnce(() => ({ someFn: mock2 })); testFn(); expect(mock2).toHaveBeenCalled(); @@ -538,7 +538,7 @@ describe('moduleMocker', () => { it('should recognize a mocked function', () => { const mockFn = moduleMocker.fn(); - expect(moduleMocker.isMockFunction(() => {})).toBe(false); + expect(moduleMocker.isMockFunction(() => { })).toBe(false); expect(moduleMocker.isMockFunction(mockFn)).toBe(true); }); @@ -593,9 +593,9 @@ describe('moduleMocker', () => { const spy = moduleMocker.spyOn(obj, 'method'); - const thisArg = {this: true}; - const firstArg = {first: true}; - const secondArg = {second: true}; + 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); @@ -626,7 +626,7 @@ describe('moduleMocker', () => { moduleMocker.spyOn({}, 'method'); }).toThrow(); expect(() => { - moduleMocker.spyOn({method: 10}, 'method'); + moduleMocker.spyOn({ method: 10 }, 'method'); }).toThrow(); }); @@ -668,13 +668,13 @@ describe('moduleMocker', () => { }); describe('spyOnProperty', () => { - it('should work', () => { + it('should work - getter', () => { let isOriginalCalled = false; let originalCallThis; let originalCallArguments; const obj = { get method() { - return function() { + return function () { isOriginalCalled = true; originalCallThis = this; originalCallArguments = arguments; @@ -684,9 +684,9 @@ describe('moduleMocker', () => { const spy = moduleMocker.spyOn(obj, 'method', 'get'); - const thisArg = {this: true}; - const firstArg = {first: true}; - const secondArg = {second: true}; + 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); @@ -709,6 +709,29 @@ describe('moduleMocker', () => { expect(spy).not.toHaveBeenCalled(); }); + it('should work - setter', () => { + const obj = { + _property: false, + set property(value) { + this._property = value; + }, + get property() { + return this._property; + } + }; + + 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('should throw on invalid input', () => { expect(() => { moduleMocker.spyOn(null, 'method'); @@ -717,7 +740,7 @@ describe('moduleMocker', () => { moduleMocker.spyOn({}, 'method'); }).toThrow(); expect(() => { - moduleMocker.spyOn({method: 10}, 'method'); + moduleMocker.spyOn({ method: 10 }, 'method'); }).toThrow(); }); @@ -726,12 +749,12 @@ describe('moduleMocker', () => { let methodTwoCalls = 0; const obj = { get methodOne() { - return function() { + return function () { methodOneCalls++; }; }, get methodTwo() { - return function() { + return function () { methodTwoCalls++; }; }, From 26b5ddb70a6ed670f9e1058d71e709987b3c64c2 Mon Sep 17 00:00:00 2001 From: phra Date: Sat, 6 Jan 2018 04:15:40 +0100 Subject: [PATCH 14/18] docs(spyOn): add example for spying on setters --- docs/JestObjectAPI.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index 337fe5118298..64dcfbf982e5 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -433,6 +433,18 @@ const video = { }, }; +module.exports = video; + +const audio = { + _volume: false, + set volume(value) { // it's a setter! + this._volume = value; + }, + get volume() { + return this._volume; + } +}; + module.exports = video; ``` @@ -451,4 +463,15 @@ test('plays video', () => { spy.mockReset(); spy.mockRestore(); }); + +test('plays audio', () => { + const spy = jest.spyOn(video, 'play', 'set'); // we pass 'set' + video.volume = 100; + + expect(spy).toHaveBeenCalled(); + expect(video.volume).toBe(100); + + spy.mockReset(); + spy.mockRestore(); +}); ``` From 95f0ab9a6f6faa989e1590ba9695af854d30d7c7 Mon Sep 17 00:00:00 2001 From: phra Date: Sat, 6 Jan 2018 04:16:19 +0100 Subject: [PATCH 15/18] style: fix eslint errors --- .../jest-mock/src/__tests__/jest_mock.test.js | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/jest-mock/src/__tests__/jest_mock.test.js b/packages/jest-mock/src/__tests__/jest_mock.test.js index a5ef4ba767bf..d298cba993de 100644 --- a/packages/jest-mock/src/__tests__/jest_mock.test.js +++ b/packages/jest-mock/src/__tests__/jest_mock.test.js @@ -21,7 +21,7 @@ describe('moduleMocker', () => { describe('getMetadata', () => { it('returns the function `name` property', () => { - function x() { } + function x() {} const metadata = moduleMocker.getMetadata(x); expect(x.name).toBe('x'); expect(metadata.name).toBe('x'); @@ -60,7 +60,7 @@ describe('moduleMocker', () => { describe('generateFromMetadata', () => { it('forwards the function name property', () => { - function foo() { } + function foo() {} const mock = moduleMocker.generateFromMetadata( moduleMocker.getMetadata(foo), ); @@ -69,8 +69,8 @@ describe('moduleMocker', () => { it('escapes illegal characters in function name property', () => { function getMockFnWithOriginalName(name) { - const fn = () => { }; - Object.defineProperty(fn, 'name', { value: name }); + const fn = () => {}; + Object.defineProperty(fn, 'name', {value: name}); return moduleMocker.generateFromMetadata(moduleMocker.getMetadata(fn)); } @@ -93,7 +93,7 @@ describe('moduleMocker', () => { }); it('special cases the mockConstructor name', () => { - function mockConstructor() { } + function mockConstructor() {} const mock = moduleMocker.generateFromMetadata( moduleMocker.getMetadata(mockConstructor), ); @@ -102,8 +102,8 @@ describe('moduleMocker', () => { }); it('wont interfere with previous mocks on a shared prototype', () => { - const ClassFoo = function () { }; - ClassFoo.prototype.x = () => { }; + const ClassFoo = function() {}; + ClassFoo.prototype.x = () => {}; const ClassFooMock = moduleMocker.generateFromMetadata( moduleMocker.getMetadata(ClassFoo), ); @@ -131,7 +131,7 @@ describe('moduleMocker', () => { }, }, nonEnumMethod: { - value: () => { }, + value: () => {}, }, }, ); @@ -166,7 +166,7 @@ describe('moduleMocker', () => { it('mocks ES2015 non-enumerable methods', () => { class ClassFoo { - foo() { } + foo() {} toString() { return 'Foo'; } @@ -190,7 +190,7 @@ describe('moduleMocker', () => { }); it('mocks methods that are bound multiple times', () => { - const func = function func() { }; + const func = function func() {}; const multipleBoundFunc = func.bind(null).bind(null); const multipleBoundFuncMock = moduleMocker.generateFromMetadata( @@ -202,7 +202,7 @@ describe('moduleMocker', () => { it('mocks methods that are bound after mocking', () => { const fooMock = moduleMocker.generateFromMetadata( - moduleMocker.getMetadata(() => { }), + moduleMocker.getMetadata(() => {}), ); const barMock = moduleMocker.generateFromMetadata( @@ -475,13 +475,13 @@ describe('moduleMocker', () => { it('should mock constructor', () => { const mock1 = jest.fn(); const mock2 = jest.fn(); - const Module = jest.fn(() => ({ someFn: mock1 })); - const testFn = function () { + const Module = jest.fn(() => ({someFn: mock1})); + const testFn = function() { const m = new Module(); m.someFn(); }; - Module.mockImplementationOnce(() => ({ someFn: mock2 })); + Module.mockImplementationOnce(() => ({someFn: mock2})); testFn(); expect(mock2).toHaveBeenCalled(); @@ -538,7 +538,7 @@ describe('moduleMocker', () => { it('should recognize a mocked function', () => { const mockFn = moduleMocker.fn(); - expect(moduleMocker.isMockFunction(() => { })).toBe(false); + expect(moduleMocker.isMockFunction(() => {})).toBe(false); expect(moduleMocker.isMockFunction(mockFn)).toBe(true); }); @@ -593,9 +593,9 @@ describe('moduleMocker', () => { const spy = moduleMocker.spyOn(obj, 'method'); - const thisArg = { this: true }; - const firstArg = { first: true }; - const secondArg = { second: true }; + 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); @@ -626,7 +626,7 @@ describe('moduleMocker', () => { moduleMocker.spyOn({}, 'method'); }).toThrow(); expect(() => { - moduleMocker.spyOn({ method: 10 }, 'method'); + moduleMocker.spyOn({method: 10}, 'method'); }).toThrow(); }); @@ -674,7 +674,7 @@ describe('moduleMocker', () => { let originalCallArguments; const obj = { get method() { - return function () { + return function() { isOriginalCalled = true; originalCallThis = this; originalCallArguments = arguments; @@ -684,9 +684,9 @@ describe('moduleMocker', () => { const spy = moduleMocker.spyOn(obj, 'method', 'get'); - const thisArg = { this: true }; - const firstArg = { first: true }; - const secondArg = { second: true }; + 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); @@ -717,7 +717,7 @@ describe('moduleMocker', () => { }, get property() { return this._property; - } + }, }; const spy = moduleMocker.spyOn(obj, 'property', 'set'); @@ -740,7 +740,7 @@ describe('moduleMocker', () => { moduleMocker.spyOn({}, 'method'); }).toThrow(); expect(() => { - moduleMocker.spyOn({ method: 10 }, 'method'); + moduleMocker.spyOn({method: 10}, 'method'); }).toThrow(); }); @@ -749,12 +749,12 @@ describe('moduleMocker', () => { let methodTwoCalls = 0; const obj = { get methodOne() { - return function () { + return function() { methodOneCalls++; }; }, get methodTwo() { - return function () { + return function() { methodTwoCalls++; }; }, From bd44ca5eba27d91d0bbe483f42823ceb4cef740b Mon Sep 17 00:00:00 2001 From: Francesco Soncina Date: Mon, 8 Jan 2018 17:59:18 +0100 Subject: [PATCH 16/18] refactor: format error messages with getErrorMsg() --- packages/jest-jasmine2/src/jasmine/spy_registry.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/jest-jasmine2/src/jasmine/spy_registry.js b/packages/jest-jasmine2/src/jasmine/spy_registry.js index 5427e811d6a1..905ae7ec8590 100644 --- a/packages/jest-jasmine2/src/jasmine/spy_registry.js +++ b/packages/jest-jasmine2/src/jasmine/spy_registry.js @@ -136,12 +136,14 @@ export default function SpyRegistry(options: Object) { this._spyOnProperty = function(obj, propertyName, accessType = 'get') { if (!obj) { throw new Error( - 'spyOn could not find an object to spy upon for ' + propertyName + '', + getErrorMsg( + 'could not find an object to spy upon for ' + propertyName, + ), ); } if (!propertyName) { - throw new Error('No property name supplied'); + throw new Error(getErrorMsg('No property name supplied')); } let descriptor; @@ -152,16 +154,18 @@ export default function SpyRegistry(options: Object) { } if (!descriptor) { - throw new Error(propertyName + ' property does not exist'); + throw new Error(getErrorMsg(propertyName + ' property does not exist')); } if (!descriptor.configurable) { - throw new Error(propertyName + ' is not declared configurable'); + throw new Error(getErrorMsg(propertyName + ' is not declared configurable')); } if (!descriptor[accessType]) { throw new Error( - 'Property ' + propertyName + ' does not have access type ' + accessType, + getErrorMsg( + 'Property ' + propertyName + ' does not have access type ' + accessType, + ) ); } From 37093f6284e9cf6d111e7a271a54950b473512da Mon Sep 17 00:00:00 2001 From: phra Date: Mon, 8 Jan 2018 18:02:43 +0100 Subject: [PATCH 17/18] style: fix eslint errors --- .../jest-jasmine2/src/jasmine/spy_registry.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/jest-jasmine2/src/jasmine/spy_registry.js b/packages/jest-jasmine2/src/jasmine/spy_registry.js index 905ae7ec8590..4a619eaba36f 100644 --- a/packages/jest-jasmine2/src/jasmine/spy_registry.js +++ b/packages/jest-jasmine2/src/jasmine/spy_registry.js @@ -136,9 +136,7 @@ export default function SpyRegistry(options: Object) { this._spyOnProperty = function(obj, propertyName, accessType = 'get') { if (!obj) { throw new Error( - getErrorMsg( - 'could not find an object to spy upon for ' + propertyName, - ), + getErrorMsg('could not find an object to spy upon for ' + propertyName), ); } @@ -158,14 +156,19 @@ export default function SpyRegistry(options: Object) { } if (!descriptor.configurable) { - throw new Error(getErrorMsg(propertyName + ' is not declared configurable')); + throw new Error( + getErrorMsg(propertyName + ' is not declared configurable'), + ); } if (!descriptor[accessType]) { throw new Error( getErrorMsg( - 'Property ' + propertyName + ' does not have access type ' + accessType, - ) + 'Property ' + + propertyName + + ' does not have access type ' + + accessType, + ), ); } From 224eb25b764c283451b715ce77cc600d210939af Mon Sep 17 00:00:00 2001 From: phra Date: Mon, 8 Jan 2018 18:08:35 +0100 Subject: [PATCH 18/18] revert: restore snapshotState.getUncheckedKeys() --- packages/jest-jasmine2/src/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/jest-jasmine2/src/index.js b/packages/jest-jasmine2/src/index.js index 630055ada8ed..8dc13639b70e 100644 --- a/packages/jest-jasmine2/src/index.js +++ b/packages/jest-jasmine2/src/index.js @@ -161,10 +161,9 @@ const addSnapshotData = (results, snapshotState) => { }); const uncheckedCount = snapshotState.getUncheckedCount(); - let uncheckedKeys; + const uncheckedKeys = snapshotState.getUncheckedKeys(); if (uncheckedCount) { - uncheckedKeys = snapshotState.getUncheckedKeys(); snapshotState.removeUncheckedKeys(); }