diff --git a/packages/jest-mock/src/__tests__/__fixtures__/class-mocks-types.ts b/packages/jest-mock/src/__tests__/__fixtures__/class-mocks-types.ts index ccb2eae1fc4f..a6f8aa5634d8 100644 --- a/packages/jest-mock/src/__tests__/__fixtures__/class-mocks-types.ts +++ b/packages/jest-mock/src/__tests__/__fixtures__/class-mocks-types.ts @@ -52,3 +52,16 @@ export default class SuperTestClass { } export class TestClass extends SuperTestClass {} + +export function testFunction1() { + return 'testFunction1'; +} + +function testFunction() { + return 'testFunction2'; +} +export const testFunction2 = testFunction; + +export const testFunction3 = () => { + return 'testFunction3'; +}; diff --git a/packages/jest-mock/src/__tests__/class-mocks-dual-import.test.ts b/packages/jest-mock/src/__tests__/class-mocks-dual-import.test.ts index 610469e9deba..3bcc759e6d68 100644 --- a/packages/jest-mock/src/__tests__/class-mocks-dual-import.test.ts +++ b/packages/jest-mock/src/__tests__/class-mocks-dual-import.test.ts @@ -35,96 +35,4 @@ describe('Testing the mocking of a class hierarchy defined in multiple imports', expect(testClassInstance.testMethod()).toBe('mockTestMethod'); expect(mockTestMethod).toHaveBeenCalledTimes(1); }); - - it('can read a value from an instance getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass.prototype, 'testAccessor', 'get') - .mockImplementation(() => { - return 'mockTestAccessor'; - }); - const testClassInstance = new SuperTestClass(); - expect(testClassInstance.testAccessor).toBe('mockTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can read a value from a superclass instance getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass.prototype, 'testAccessor', 'get') - .mockImplementation(() => { - return 'mockTestAccessor'; - }); - const testClassInstance = new TestClass(); - expect(testClassInstance.testAccessor).toBe('mockTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); - - it('can write a value to an instance setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass.prototype, 'testAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - const testClassInstance = new SuperTestClass(); - testClassInstance.testAccessor = ''; - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can write a value to a superclass instance setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass.prototype, 'testAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - const testClassInstance = new TestClass(); - testClassInstance.testAccessor = ''; - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); - - it('can read a value from a static getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass, 'staticTestAccessor', 'get') - .mockImplementation(() => { - return 'mockStaticTestAccessor'; - }); - expect(SuperTestClass.staticTestAccessor).toBe('mockStaticTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can read a value from a superclass static getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass, 'staticTestAccessor', 'get') - .mockImplementation(() => { - return 'mockStaticTestAccessor'; - }); - expect(TestClass.staticTestAccessor).toBe('mockStaticTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); - - it('can write a value to a static setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass, 'staticTestAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - SuperTestClass.staticTestAccessor = ''; - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can write a value to a superclass static setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass, 'staticTestAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - TestClass.staticTestAccessor = ''; - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); }); diff --git a/packages/jest-mock/src/__tests__/class-mocks-single-import.test.ts b/packages/jest-mock/src/__tests__/class-mocks-single-import.test.ts index 9b354db44d95..078c0531ddf2 100644 --- a/packages/jest-mock/src/__tests__/class-mocks-single-import.test.ts +++ b/packages/jest-mock/src/__tests__/class-mocks-single-import.test.ts @@ -6,9 +6,32 @@ * */ -import SuperTestClass, {TestClass} from './__fixtures__/class-mocks-types'; +import SuperTestClass, * as testTypes from './__fixtures__/class-mocks-types'; jest.mock('./__fixtures__/class-mocks-types'); +describe('Testing the mocking of exported functions', () => { + it('can mock a directly exported function', () => { + jest.spyOn(testTypes, 'testFunction1').mockImplementation(() => { + return 'mockTestFunction'; + }); + expect(testTypes.testFunction1()).toBe('mockTestFunction'); + }); + + it('can mock an indirectly exported function', () => { + jest.spyOn(testTypes, 'testFunction2').mockImplementation(() => { + return 'mockTestFunction'; + }); + expect(testTypes.testFunction2()).toBe('mockTestFunction'); + }); + + it('can mock an indirectly exported anonymous function', () => { + jest.spyOn(testTypes, 'testFunction3').mockImplementation(() => { + return 'mockTestFunction'; + }); + expect(testTypes.testFunction3()).toBe('mockTestFunction'); + }); +}); + describe('Testing the mocking of a class hierarchy defined in a single import', () => { it('can call an instance method - Auto-mocked class', () => { const mockTestMethod = jest @@ -25,11 +48,11 @@ describe('Testing the mocking of a class hierarchy defined in a single import', it('can call a superclass instance method - Auto-mocked class', () => { const mockTestMethod = jest - .spyOn(TestClass.prototype, 'testMethod') + .spyOn(testTypes.TestClass.prototype, 'testMethod') .mockImplementation(() => { return 'mockTestMethod'; }); - const testClassInstance = new TestClass(); + const testClassInstance = new testTypes.TestClass(); expect(testClassInstance.testMethod()).toBe('mockTestMethod'); expect(mockTestMethod).toHaveBeenCalledTimes(1); }); @@ -49,11 +72,11 @@ describe('Testing the mocking of a class hierarchy defined in a single import', it('can call a superclass instance method named "get" - Auto-mocked class', () => { const mockTestMethod = jest - .spyOn(TestClass.prototype, 'get') + .spyOn(testTypes.TestClass.prototype, 'get') .mockImplementation(() => { return 'mockTestMethod'; }); - const testClassInstance = new TestClass(); + const testClassInstance = new testTypes.TestClass(); expect(testClassInstance.get()).toBe('mockTestMethod'); expect(mockTestMethod).toHaveBeenCalledTimes(1); @@ -75,65 +98,17 @@ describe('Testing the mocking of a class hierarchy defined in a single import', it('can call a superclass instance method named "set" - Auto-mocked class', () => { const mockTestMethod = jest - .spyOn(TestClass.prototype, 'set') + .spyOn(testTypes.TestClass.prototype, 'set') .mockImplementation(() => { return 'mockTestMethod'; }); - const testClassInstance = new TestClass(); + const testClassInstance = new testTypes.TestClass(); expect(testClassInstance.set()).toBe('mockTestMethod'); expect(mockTestMethod).toHaveBeenCalledTimes(1); mockTestMethod.mockClear(); }); - it('can read a value from an instance getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass.prototype, 'testAccessor', 'get') - .mockImplementation(() => { - return 'mockTestAccessor'; - }); - const testClassInstance = new SuperTestClass(); - expect(testClassInstance.testAccessor).toBe('mockTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can read a value from a superclass instance getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass.prototype, 'testAccessor', 'get') - .mockImplementation(() => { - return 'mockTestAccessor'; - }); - const testClassInstance = new TestClass(); - expect(testClassInstance.testAccessor).toBe('mockTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); - - it('can write a value to an instance setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass.prototype, 'testAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - const testClassInstance = new SuperTestClass(); - testClassInstance.testAccessor = ''; - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can write a value to a superclass instance setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass.prototype, 'testAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - const testClassInstance = new TestClass(); - testClassInstance.testAccessor = ''; - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); - it('can call a static method - Auto-mocked class', () => { const mockTestMethod = jest .spyOn(SuperTestClass, 'staticTestMethod') @@ -148,11 +123,11 @@ describe('Testing the mocking of a class hierarchy defined in a single import', it('can call a superclass static method - Auto-mocked class', () => { const mockTestMethod = jest - .spyOn(TestClass, 'staticTestMethod') + .spyOn(testTypes.TestClass, 'staticTestMethod') .mockImplementation(() => { return 'mockTestMethod'; }); - expect(TestClass.staticTestMethod()).toBe('mockTestMethod'); + expect(testTypes.TestClass.staticTestMethod()).toBe('mockTestMethod'); expect(mockTestMethod).toHaveBeenCalledTimes(1); }); @@ -170,11 +145,11 @@ describe('Testing the mocking of a class hierarchy defined in a single import', it('can call a superclass static method named "get" - Auto-mocked class', () => { const mockTestMethod = jest - .spyOn(TestClass, 'get') + .spyOn(testTypes.TestClass, 'get') .mockImplementation(() => { return 'mockTestMethod'; }); - expect(TestClass.get()).toBe('mockTestMethod'); + expect(testTypes.TestClass.get()).toBe('mockTestMethod'); expect(mockTestMethod).toHaveBeenCalledTimes(1); mockTestMethod.mockClear(); @@ -194,57 +169,13 @@ describe('Testing the mocking of a class hierarchy defined in a single import', it('can call a superclass static method named "set" - Auto-mocked class', () => { const mockTestMethod = jest - .spyOn(TestClass, 'set') + .spyOn(testTypes.TestClass, 'set') .mockImplementation(() => { return 'mockTestMethod'; }); - expect(TestClass.set()).toBe('mockTestMethod'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can read a value from a static getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass, 'staticTestAccessor', 'get') - .mockImplementation(() => { - return 'mockStaticTestAccessor'; - }); - expect(SuperTestClass.staticTestAccessor).toBe('mockStaticTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - - mockTestMethod.mockClear(); - }); - - it('can read a value from a superclass static getter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass, 'staticTestAccessor', 'get') - .mockImplementation(() => { - return 'mockStaticTestAccessor'; - }); - expect(TestClass.staticTestAccessor).toBe('mockStaticTestAccessor'); - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); - - it('can write a value to a static setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(SuperTestClass, 'staticTestAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - SuperTestClass.staticTestAccessor = ''; + expect(testTypes.TestClass.set()).toBe('mockTestMethod'); expect(mockTestMethod).toHaveBeenCalledTimes(1); mockTestMethod.mockClear(); }); - - it('can write a value to a superclass static setter - Auto-mocked class', () => { - const mockTestMethod = jest - .spyOn(TestClass, 'staticTestAccessor', 'set') - .mockImplementation((_x: string) => { - return () => {}; - }); - TestClass.staticTestAccessor = ''; - expect(mockTestMethod).toHaveBeenCalledTimes(1); - }); }); diff --git a/packages/jest-mock/src/__tests__/class-mocks.test.ts b/packages/jest-mock/src/__tests__/class-mocks.test.ts index 4b33c6c532dd..ea6b92f66465 100644 --- a/packages/jest-mock/src/__tests__/class-mocks.test.ts +++ b/packages/jest-mock/src/__tests__/class-mocks.test.ts @@ -14,11 +14,14 @@ describe('Testing the mocking of a class', () => { } } - jest.spyOn(TestClass.prototype, 'testMethod').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass.prototype, 'testMethod') + .mockImplementation(() => 'mockTestMethod'); const testClassInstance = new TestClass(); expect(testClassInstance.testMethod()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.testMethod()).toBe('testMethod'); }); it('can call a superclass instance method', () => { @@ -30,11 +33,18 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest.spyOn(TestClass.prototype, 'testMethod').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass.prototype, 'testMethod') + .mockImplementation(() => { + return 'mockTestMethod'; + }); const testClassInstance = new TestClass(); expect(testClassInstance.testMethod()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.testMethod()).toBe('testMethod'); + // eslint-disable-next-line no-prototype-builtins + expect(TestClass.prototype.hasOwnProperty('testMethod')).toBe(false); }); it('can call an instance method named "get"', () => { @@ -44,11 +54,16 @@ describe('Testing the mocking of a class', () => { } } - jest.spyOn(TestClass.prototype, 'get').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass.prototype, 'get') + .mockImplementation(() => { + return 'mockTestMethod'; + }); const testClassInstance = new TestClass(); expect(testClassInstance.get()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.get()).toBe('get'); }); it('can call a superclass instance method named "get"', () => { @@ -60,11 +75,18 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest.spyOn(TestClass.prototype, 'get').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass.prototype, 'get') + .mockImplementation(() => { + return 'mockTestMethod'; + }); const testClassInstance = new TestClass(); expect(testClassInstance.get()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.get()).toBe('get'); + // eslint-disable-next-line no-prototype-builtins + expect(TestClass.prototype.hasOwnProperty('get')).toBe(false); }); it('can call an instance method named "set"', () => { @@ -74,11 +96,16 @@ describe('Testing the mocking of a class', () => { } } - jest.spyOn(TestClass.prototype, 'set').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass.prototype, 'set') + .mockImplementation(() => { + return 'mockTestMethod'; + }); const testClassInstance = new TestClass(); expect(testClassInstance.set()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.set()).toBe('set'); }); it('can call a superclass instance method named "set"', () => { @@ -90,11 +117,18 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest.spyOn(TestClass.prototype, 'set').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass.prototype, 'set') + .mockImplementation(() => { + return 'mockTestMethod'; + }); const testClassInstance = new TestClass(); expect(testClassInstance.set()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.set()).toBe('set'); + // eslint-disable-next-line no-prototype-builtins + expect(TestClass.prototype.hasOwnProperty('set')).toBe(false); }); it('can read a value from an instance getter', () => { @@ -104,13 +138,16 @@ describe('Testing the mocking of a class', () => { } } - jest + const mockFn = jest .spyOn(TestClass.prototype, 'testMethod', 'get') .mockImplementation(() => { return 'mockTestMethod'; }); const testClassInstance = new TestClass(); expect(testClassInstance.testMethod).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.testMethod).toBe('testMethod'); }); it('can read a value from an superclass instance getter', () => { @@ -122,13 +159,16 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest + const mockFn = jest .spyOn(TestClass.prototype, 'testMethod', 'get') .mockImplementation(() => { return 'mockTestMethod'; }); const testClassInstance = new TestClass(); expect(testClassInstance.testMethod).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(testClassInstance.testMethod).toBe('testMethod'); }); it('can write a value to an instance setter', () => { @@ -147,6 +187,10 @@ describe('Testing the mocking of a class', () => { const testClassInstance = new TestClass(); testClassInstance.testMethod = ''; expect(mocktestMethod).toHaveBeenCalledTimes(1); + + mocktestMethod.mockRestore(); + testClassInstance.testMethod = ''; + expect(mocktestMethod).toHaveBeenCalledTimes(0); }); it('can write a value to a superclass instance setter', () => { @@ -167,6 +211,10 @@ describe('Testing the mocking of a class', () => { const testClassInstance = new TestClass(); testClassInstance.testMethod = ''; expect(mocktestMethod).toHaveBeenCalledTimes(1); + + mocktestMethod.mockRestore(); + testClassInstance.testMethod = ''; + expect(mocktestMethod).toHaveBeenCalledTimes(0); }); it('can call a static method', () => { @@ -176,10 +224,15 @@ describe('Testing the mocking of a class', () => { } } - jest.spyOn(TestClass, 'testMethod').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass, 'testMethod') + .mockImplementation(() => { + return 'mockTestMethod'; + }); expect(TestClass.testMethod()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.testMethod()).toBe('testMethod'); }); it('can call a superclass static method', () => { @@ -191,10 +244,15 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest.spyOn(TestClass, 'testMethod').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass, 'testMethod') + .mockImplementation(() => { + return 'mockTestMethod'; + }); expect(TestClass.testMethod()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.testMethod()).toBe('testMethod'); }); it('can call a static method named "get"', () => { @@ -204,10 +262,13 @@ describe('Testing the mocking of a class', () => { } } - jest.spyOn(TestClass, 'get').mockImplementation(() => { + const mockFn = jest.spyOn(TestClass, 'get').mockImplementation(() => { return 'mockTestMethod'; }); expect(TestClass.get()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.get()).toBe('get'); }); it('can call a superclass static method named "get"', () => { @@ -219,10 +280,15 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest.spyOn(TestClass, 'get').mockImplementation(() => { + const mockFn = jest.spyOn(TestClass, 'get').mockImplementation(() => { return 'mockTestMethod'; }); expect(TestClass.get()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.get()).toBe('get'); + // eslint-disable-next-line no-prototype-builtins + expect(TestClass.hasOwnProperty('get')).toBe(false); }); it('can call a static method named "set"', () => { @@ -232,10 +298,13 @@ describe('Testing the mocking of a class', () => { } } - jest.spyOn(TestClass, 'set').mockImplementation(() => { + const mockFn = jest.spyOn(TestClass, 'set').mockImplementation(() => { return 'mockTestMethod'; }); expect(TestClass.set()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.set()).toBe('set'); }); it('can call a superclass static method named "set"', () => { @@ -247,10 +316,15 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest.spyOn(TestClass, 'set').mockImplementation(() => { + const mockFn = jest.spyOn(TestClass, 'set').mockImplementation(() => { return 'mockTestMethod'; }); expect(TestClass.set()).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.set()).toBe('set'); + // eslint-disable-next-line no-prototype-builtins + expect(TestClass.hasOwnProperty('set')).toBe(false); }); it('can read a value from a static getter', () => { @@ -260,10 +334,15 @@ describe('Testing the mocking of a class', () => { } } - jest.spyOn(TestClass, 'testMethod', 'get').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass, 'testMethod', 'get') + .mockImplementation(() => { + return 'mockTestMethod'; + }); expect(TestClass.testMethod).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.testMethod).toBe('testMethod'); }); it('can read a value from a superclass static getter', () => { @@ -275,10 +354,15 @@ describe('Testing the mocking of a class', () => { class TestClass extends SuperTestClass {} - jest.spyOn(TestClass, 'testMethod', 'get').mockImplementation(() => { - return 'mockTestMethod'; - }); + const mockFn = jest + .spyOn(TestClass, 'testMethod', 'get') + .mockImplementation(() => { + return 'mockTestMethod'; + }); expect(TestClass.testMethod).toBe('mockTestMethod'); + + mockFn.mockRestore(); + expect(TestClass.testMethod).toBe('testMethod'); }); it('can write a value to a static setter', () => { @@ -296,6 +380,9 @@ describe('Testing the mocking of a class', () => { }); TestClass.testMethod = ''; expect(mocktestMethod).toHaveBeenCalledTimes(1); + + mocktestMethod.mockRestore(); + expect(mocktestMethod).toHaveBeenCalledTimes(0); }); it('can write a value to a superclass static setter', () => { diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index 8f1436f4b042..71680852eca7 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -1264,7 +1264,9 @@ describe('moduleMocker', () => { }).toThrow('spyOn could not find an object to spy upon for method'); expect(() => { moduleMocker.spyOn({}, 'method'); - }).toThrow('method property does not exist'); + }).toThrow( + 'Cannot spy the method property because it is not a function; undefined given instead', + ); expect(() => { moduleMocker.spyOn({method: 10}, 'method'); }).toThrow( @@ -1432,7 +1434,9 @@ describe('moduleMocker', () => { }).toThrow('spyOn could not find an object to spy upon for method'); expect(() => { moduleMocker.spyOn({}, 'method'); - }).toThrow('method property does not exist'); + }).toThrow( + 'Cannot spy the method property because it is not a function; undefined given instead', + ); expect(() => { moduleMocker.spyOn({method: 10}, 'method'); }).toThrow( diff --git a/packages/jest-mock/src/__tests__/window-spy.test.ts b/packages/jest-mock/src/__tests__/window-spy.test.ts new file mode 100644 index 000000000000..43285c041706 --- /dev/null +++ b/packages/jest-mock/src/__tests__/window-spy.test.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @jest-environment jsdom + */ + +/// + +/* eslint-env browser*/ + +function exampleDispatch() { + window.dispatchEvent(new CustomEvent('event', {})); +} + +describe('spy on `dispatchEvent`', () => { + const dispatchEventSpy = jest.spyOn(window, 'dispatchEvent'); + + it('should be called', () => { + exampleDispatch(); + + expect(dispatchEventSpy).toHaveBeenCalled(); + }); +}); diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index d0c6ba44ac8c..3c0b6f108bd5 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -535,10 +535,7 @@ export class ModuleMocker { if (!isReadonlyProp(object, prop)) { const propDesc = Object.getOwnPropertyDescriptor(object, prop); - if ( - propDesc !== undefined && - !(propDesc.get && prop == '__proto__') - ) { + if ((propDesc !== undefined && !propDesc.get) || object.__esModule) { slots.add(prop); } } @@ -928,9 +925,7 @@ export class ModuleMocker { } this._getSlots(metadata.members).forEach(slot => { - let slotMock: Mocked; const slotMetadata = (metadata.members && metadata.members[slot]) || {}; - if (slotMetadata.ref != null) { callbacks.push( (function (ref) { @@ -938,40 +933,7 @@ export class ModuleMocker { })(slotMetadata.ref), ); } else { - slotMock = this._generateMock(slotMetadata, callbacks, refs); - - // For superclass accessor properties the subclass metadata contains the definitions - // for the getter and setter methods, and the superclass refs to them. - // The mock implementations are not available until the callbacks have been executed. - // Missing getter and setter refs will be resolved as their callbacks have been - // stacked before the setting of the accessor definition is stacked. - - // In some cases, e.g. third-party APIs, a 'prototype' ancestor to be - // mocked has a function property called 'get'. In this circumstance - // the 'prototype' property cannot be redefined and doing so causes an - // exception. Checks have been added for the 'configurable' and - // 'enumberable' properties present on true accessor property - // descriptors to prevent the attempt to replace the API. - if ( - (slotMetadata.members?.get?.ref !== undefined || - slotMetadata.members?.set?.ref !== undefined) && - slotMetadata.members?.configurable && - slotMetadata.members?.enumerable - ) { - callbacks.push( - (function (ref) { - return () => Object.defineProperty(mock, slot, ref); - })(slotMock as PropertyDescriptor), - ); - } else if ( - (slotMetadata.members?.get || slotMetadata.members?.set) && - slotMetadata.members?.configurable && - slotMetadata.members?.enumerable - ) { - Object.defineProperty(mock, slot, slotMock as PropertyDescriptor); - } else { - mock[slot] = slotMock; - } + mock[slot] = this._generateMock(slotMetadata, callbacks, refs); } }); @@ -1055,33 +1017,8 @@ export class ModuleMocker { ) { return; } - - let descriptor = Object.getOwnPropertyDescriptor(component, slot); - let proto = Object.getPrototypeOf(component); - while (!descriptor && proto !== null) { - descriptor = Object.getOwnPropertyDescriptor(proto, slot); - proto = Object.getPrototypeOf(proto); - } - - let slotMetadata: MockMetadata | null = null; - if (descriptor?.get || descriptor?.set) { - // Specific case required for mocking class definitions imported via modules. - // In this case the class definitions are stored in accessor properties. - // All getters were previously ignored except where the containing object had __esModule == true - // Now getters are mocked the class definitions must still be read. - // @ts-expect-error ignore type mismatch - if (component.__esModule) { - // @ts-expect-error no index signature - slotMetadata = this.getMetadata(component[slot], refs); - } else { - // @ts-expect-error ignore type mismatch - slotMetadata = this.getMetadata(descriptor, refs); - } - } else { - // @ts-expect-error no index signature - slotMetadata = this.getMetadata(component[slot], refs); - } - + // @ts-expect-error no index signature + const slotMetadata = this.getMetadata(component[slot], refs); if (slotMetadata) { if (!members) { members = {}; @@ -1151,6 +1088,12 @@ export class ModuleMocker { methodKey: K, accessType?: 'get' | 'set', ) { + if (typeof object !== 'object' && typeof object !== 'function') { + throw new Error( + `Cannot spyOn on a primitive value; ${this._typeOf(object)} given`, + ); + } + if (!object) { throw new Error( `spyOn could not find an object to spy upon for ${String(methodKey)}`, @@ -1161,120 +1104,124 @@ export class ModuleMocker { throw new Error('No property name supplied'); } - if (accessType && accessType != 'get' && accessType != 'set') { - throw new Error('Invalid accessType supplied'); + if (accessType) { + return this._spyOnProperty(object, methodKey, accessType); } - if (typeof object !== 'object' && typeof object !== 'function') { - throw new Error( - `Cannot spyOn on a primitive value; ${this._typeOf(object)} given`, + const original = object[methodKey]; + + if (!this.isMockFunction(original)) { + if (typeof original !== 'function') { + throw new Error( + `Cannot spy the ${String( + methodKey, + )} property because it is not a function; ${this._typeOf( + original, + )} given instead`, + ); + } + + const isMethodOwner = Object.prototype.hasOwnProperty.call( + object, + methodKey, ); + + let descriptor = Object.getOwnPropertyDescriptor(object, methodKey); + let proto = Object.getPrototypeOf(object); + + while (!descriptor && proto !== null) { + descriptor = Object.getOwnPropertyDescriptor(proto, methodKey); + proto = Object.getPrototypeOf(proto); + } + + let mock: Mock; + + if (descriptor && descriptor.get) { + const originalGet = descriptor.get; + mock = this._makeComponent({type: 'function'}, () => { + descriptor!.get = originalGet; + Object.defineProperty(object, methodKey, descriptor!); + }); + descriptor.get = () => mock; + Object.defineProperty(object, methodKey, descriptor); + } else { + mock = this._makeComponent({type: 'function'}, () => { + if (isMethodOwner) { + object[methodKey] = original; + } else { + delete object[methodKey]; + } + }); + // @ts-expect-error overriding original method with a Mock + object[methodKey] = mock; + } + + mock.mockImplementation(function (this: unknown) { + return original.apply(this, arguments); + }); } - let descriptor = Object.getOwnPropertyDescriptor(object, methodKey); - let proto = Object.getPrototypeOf(object); + return object[methodKey]; + } + + private _spyOnProperty>( + obj: T, + propertyKey: K, + accessType: 'get' | 'set', + ): Mock<() => T> { + let descriptor = Object.getOwnPropertyDescriptor(obj, propertyKey); + let proto = Object.getPrototypeOf(obj); + while (!descriptor && proto !== null) { - descriptor = Object.getOwnPropertyDescriptor(proto, methodKey); + descriptor = Object.getOwnPropertyDescriptor(proto, propertyKey); proto = Object.getPrototypeOf(proto); } + if (!descriptor) { - throw new Error(`${String(methodKey)} property does not exist`); - } - if (!descriptor.configurable) { - throw new Error(`${String(methodKey)} is not declared configurable`); + throw new Error(`${String(propertyKey)} property does not exist`); } - if (this.isMockFunction(descriptor.value)) { - return object[methodKey]; - } else if (accessType == 'get' && this.isMockFunction(descriptor.get)) { - return descriptor.get; - } else if (accessType == 'set' && this.isMockFunction(descriptor.set)) { - return descriptor.set; + if (!descriptor.configurable) { + throw new Error(`${String(propertyKey)} is not declared configurable`); } - if (accessType) { - if (typeof descriptor[accessType] !== 'function') { - throw new Error( - `Cannot spy the ${String(accessType)} ${String( - methodKey, - )} property because it is not a function; - ${this._typeOf(descriptor?.[accessType])} given instead`, - ); - } - } else if (typeof descriptor.value !== 'function') { + if (!descriptor[accessType]) { throw new Error( - `Cannot spy the ${String( - methodKey, - )} property because it is not a function; ${this._typeOf( - descriptor.value, - )} given instead`, + `Property ${String( + propertyKey, + )} does not have access type ${accessType}`, ); } - let mock: Mock; + const original = descriptor[accessType]; - if (accessType == 'get' && descriptor.get) { - const originalAccessor = descriptor.get; - mock = this._makeComponent( - { - type: 'function', - }, - () => { - descriptor![accessType] = originalAccessor; - Object.defineProperty(object, methodKey, descriptor!); - }, - ); - - descriptor[accessType] = mock; - mock.mockImplementation(function (this: unknown) { - return originalAccessor.call(this); - }); - Object.defineProperty(object, methodKey, descriptor); - } else if (accessType == 'set' && descriptor.set) { - const originalAccessor = descriptor.set; - mock = this._makeComponent( - { - type: 'function', - }, - () => { - descriptor![accessType] = originalAccessor; - Object.defineProperty(object, methodKey, descriptor!); - }, - ); + if (!this.isMockFunction(original)) { + if (typeof original !== 'function') { + throw new Error( + `Cannot spy the ${String( + propertyKey, + )} property because it is not a function; ${this._typeOf( + original, + )} given instead`, + ); + } - descriptor[accessType] = mock; - mock.mockImplementation(function (this: unknown) { - return originalAccessor.call(this, arguments[0]); + descriptor[accessType] = this._makeComponent({type: 'function'}, () => { + // @ts-expect-error: mock is assignable + descriptor![accessType] = original; + Object.defineProperty(obj, propertyKey, descriptor!); }); - Object.defineProperty(object, methodKey, descriptor); - } else { - const isMethodOwner = Object.prototype.hasOwnProperty.call( - object, - methodKey, - ); - const original = descriptor; - mock = this._makeComponent( - { - type: 'function', - }, - () => { - if (isMethodOwner) { - object[methodKey] = original.value; - } else { - delete object[methodKey]; - } - }, - ); - - // @ts-expect-error overriding original method with a Mock - object[methodKey] = mock; - mock.mockImplementation(function (this: unknown) { - return original.value.apply(this, arguments); + (descriptor[accessType] as Mock<() => T>).mockImplementation(function ( + this: unknown, + ) { + // @ts-expect-error - wrong context + return original.apply(this, arguments); }); } - return mock; + Object.defineProperty(obj, propertyKey, descriptor); + return descriptor[accessType] as Mock<() => T>; } clearAllMocks(): void { @@ -1291,7 +1238,7 @@ export class ModuleMocker { this._spyState = new Set(); } - private _typeOf(value: any): string { + private _typeOf(value: unknown): string { return value == null ? `${value}` : typeof value; }