diff --git a/CHANGELOG.md b/CHANGELOG.md index c5cdc9aebf2d..acaed9f5f311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - `[jest-mock]` Improve `isMockFunction` to infer types of passed function ([#12442](https://github.com/facebook/jest/pull/12442)) - `[jest-mock]` [**BREAKING**] Improve the usage of `jest.fn` generic type argument ([#12489](https://github.com/facebook/jest/pull/12489)) - `[jest-mock]` Add support for auto-mocking async generator functions ([#11080](https://github.com/facebook/jest/pull/11080)) +- `[jest-mock]` Add `contexts` member to mock functions ([#12601](https://github.com/facebook/jest/pull/12601)) - `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373)) - `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392)) - `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540)) diff --git a/docs/CLI.md b/docs/CLI.md index 7263a4676883..8a213e989bbc 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -158,7 +158,7 @@ Clearing the cache will reduce performance. ### `--clearMocks` -Automatically clear mock calls, instances and results before every test. Equivalent to calling [`jest.clearAllMocks()`](JestObjectAPI.md#jestclearallmocks) before each test. This does not remove any mock implementation that may have been provided. +Automatically clear mock calls, instances, contexts and results before every test. Equivalent to calling [`jest.clearAllMocks()`](JestObjectAPI.md#jestclearallmocks) before each test. This does not remove any mock implementation that may have been provided. ### `--collectCoverageFrom=` diff --git a/docs/Configuration.md b/docs/Configuration.md index 8835cc19c459..74ab2248e05e 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -150,7 +150,7 @@ Jest attempts to scan your dependency tree once (up-front) and cache it in order Default: `false` -Automatically clear mock calls, instances and results before every test. Equivalent to calling [`jest.clearAllMocks()`](JestObjectAPI.md#jestclearallmocks) before each test. This does not remove any mock implementation that may have been provided. +Automatically clear mock calls, instances, contexts and results before every test. Equivalent to calling [`jest.clearAllMocks()`](JestObjectAPI.md#jestclearallmocks) before each test. This does not remove any mock implementation that may have been provided. ### `collectCoverage` \[boolean] diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index ed99e97fc3d0..aff9a86b5ff6 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -570,7 +570,7 @@ test('plays audio', () => { ### `jest.clearAllMocks()` -Clears the `mock.calls`, `mock.instances` and `mock.results` properties of all mocks. Equivalent to calling [`.mockClear()`](MockFunctionAPI.md#mockfnmockclear) on every mocked function. +Clears the `mock.calls`, `mock.instances`, `mock.contexts` and `mock.results` properties of all mocks. Equivalent to calling [`.mockClear()`](MockFunctionAPI.md#mockfnmockclear) on every mocked function. Returns the `jest` object for chaining. diff --git a/docs/MockFunctionAPI.md b/docs/MockFunctionAPI.md index 35a28365e986..0ac4de121f65 100644 --- a/docs/MockFunctionAPI.md +++ b/docs/MockFunctionAPI.md @@ -92,6 +92,27 @@ mockFn.mock.instances[0] === a; // true mockFn.mock.instances[1] === b; // true ``` +### `mockFn.mock.contexts` + +An array that contains the contexts for all calls of the mock function. + +A context is the `this` value that a function receives when called. The context can be set using `Function.prototype.bind`, `Function.prototype.call` or `Function.prototype.apply`. + +For example: + +```js +const mockFn = jest.fn(); + +const boundMockFn = mockFn.bind(thisContext0); +boundMockFn('a', 'b'); +mockFn.call(thisContext1, 'a', 'b'); +mockFn.apply(thisContext2, ['a', 'b']); + +mockFn.mock.contexts[0] === thisContext0; // true +mockFn.mock.contexts[1] === thisContext1; // true +mockFn.mock.contexts[2] === thisContext2; // true +``` + ### `mockFn.mock.lastCall` An array containing the call arguments of the last call that was made to this mock function. If the function was not called, it will return `undefined`. @@ -104,9 +125,9 @@ For example: A mock function `f` that has been called twice, with the arguments ### `mockFn.mockClear()` -Clears all information stored in the [`mockFn.mock.calls`](#mockfnmockcalls), [`mockFn.mock.instances`](#mockfnmockinstances) and [`mockFn.mock.results`](#mockfnmockresults) arrays. Often this is useful when you want to clean up a mocks usage data between two assertions. +Clears all information stored in the [`mockFn.mock.calls`](#mockfnmockcalls), [`mockFn.mock.instances`](#mockfnmockinstances), [`mockFn.mock.contexts`](#mockfnmockcontexts) and [`mockFn.mock.results`](#mockfnmockresults) arrays. Often this is useful when you want to clean up a mocks usage data between two assertions. -Beware that `mockFn.mockClear()` will replace `mockFn.mock`, not just these three properties! You should, therefore, avoid assigning `mockFn.mock` to other variables, temporary or not, to make sure you don't access stale data. +Beware that `mockFn.mockClear()` will replace `mockFn.mock`, not just reset the values of its properties! You should, therefore, avoid assigning `mockFn.mock` to other variables, temporary or not, to make sure you don't access stale data. The [`clearMocks`](configuration#clearmocks-boolean) configuration option is available to clear mocks automatically before each tests. diff --git a/docs/MockFunctions.md b/docs/MockFunctions.md index 38870027a515..d1f1752de2d1 100644 --- a/docs/MockFunctions.md +++ b/docs/MockFunctions.md @@ -43,15 +43,17 @@ expect(mockCallback.mock.results[0].value).toBe(42); All mock functions have this special `.mock` property, which is where data about how the function has been called and what the function returned is kept. The `.mock` property also tracks the value of `this` for each call, so it is possible to inspect this as well: ```javascript -const myMock = jest.fn(); +const myMock1 = jest.fn(); +const a = new myMock1(); +console.log(myMock1.mock.instances); +// > [ ] -const a = new myMock(); +const myMock2 = jest.fn(); const b = {}; -const bound = myMock.bind(b); +const bound = myMock2.bind(b); bound(); - -console.log(myMock.mock.instances); -// > [ , ] +console.log(myMock2.mock.contexts); +// > [ ] ``` These mock members are very useful in tests to assert how these functions get called, instantiated, or what they returned: @@ -69,6 +71,9 @@ expect(someMockFunction.mock.calls[0][1]).toBe('second arg'); // The return value of the first call to the function was 'return value' expect(someMockFunction.mock.results[0].value).toBe('return value'); +// The function was called with a certain `this` context: the `element` object. +expect(someMockFunction.mock.contexts[0]).toBe(element); + // This function was instantiated exactly twice expect(someMockFunction.mock.instances.length).toBe(2); diff --git a/packages/jest-cli/src/cli/args.ts b/packages/jest-cli/src/cli/args.ts index 57c55a59f129..289c737dd8fe 100644 --- a/packages/jest-cli/src/cli/args.ts +++ b/packages/jest-cli/src/cli/args.ts @@ -154,7 +154,7 @@ export const options = { }, clearMocks: { description: - 'Automatically clear mock calls, instances and results before every test. ' + + 'Automatically clear mock calls, instances, contexts and results before every test. ' + 'Equivalent to calling jest.clearAllMocks() before each test.', type: 'boolean', }, diff --git a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap index fbb7b4b2ad05..2c5c94dd9d4b 100644 --- a/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap +++ b/packages/jest-cli/src/init/__tests__/__snapshots__/init.test.js.snap @@ -108,7 +108,7 @@ Array [ }, Object { "initial": false, - "message": "Automatically clear mock calls, instances and results before every test?", + "message": "Automatically clear mock calls, instances, contexts and results before every test?", "name": "clearMocks", "type": "confirm", }, @@ -131,7 +131,7 @@ module.exports = { // The directory where Jest should store its cached dependency information // cacheDirectory: "/tmp/jest", - // Automatically clear mock calls, instances and results before every test + // Automatically clear mock calls, instances, contexts and results before every test // clearMocks: false, // Indicates whether the coverage information should be collected while executing the test diff --git a/packages/jest-cli/src/init/questions.ts b/packages/jest-cli/src/init/questions.ts index fd4e9325ff7f..9d86faa6ccc2 100644 --- a/packages/jest-cli/src/init/questions.ts +++ b/packages/jest-cli/src/init/questions.ts @@ -43,7 +43,7 @@ const defaultQuestions: Array = [ { initial: false, message: - 'Automatically clear mock calls, instances and results before every test?', + 'Automatically clear mock calls, instances, contexts and results before every test?', name: 'clearMocks', type: 'confirm', }, diff --git a/packages/jest-config/src/Descriptions.ts b/packages/jest-config/src/Descriptions.ts index 795637460a1d..9f4549e5fb22 100644 --- a/packages/jest-config/src/Descriptions.ts +++ b/packages/jest-config/src/Descriptions.ts @@ -13,7 +13,7 @@ const descriptions: {[key in keyof Config.InitialOptions]: string} = { cacheDirectory: 'The directory where Jest should store its cached dependency information', clearMocks: - 'Automatically clear mock calls, instances and results before every test', + 'Automatically clear mock calls, instances, contexts and results before every test', collectCoverage: 'Indicates whether the coverage information should be collected while executing the test', collectCoverageFrom: diff --git a/packages/jest-environment/src/index.ts b/packages/jest-environment/src/index.ts index ac7fe89c30cc..6a96143ada73 100644 --- a/packages/jest-environment/src/index.ts +++ b/packages/jest-environment/src/index.ts @@ -71,7 +71,7 @@ export interface Jest { */ autoMockOn(): Jest; /** - * Clears the `mock.calls`, `mock.instances` and `mock.results` properties of + * Clears the `mock.calls`, `mock.instances`, `mock.contexts` and `mock.results` properties of * all mocks. Equivalent to calling `.mockClear()` on every mocked function. */ clearAllMocks(): Jest; diff --git a/packages/jest-mock/__typetests__/mock-functions.test.ts b/packages/jest-mock/__typetests__/mock-functions.test.ts index e1ace1d254f4..6be5a35b7b77 100644 --- a/packages/jest-mock/__typetests__/mock-functions.test.ts +++ b/packages/jest-mock/__typetests__/mock-functions.test.ts @@ -66,7 +66,8 @@ expectType never>>( ); expectError(fn('moduleName')); -const mockFn = fn((a: string, b?: number) => true); +declare const mockFnImpl: (this: Date, a: string, b?: number) => boolean; +const mockFn = fn(mockFnImpl); const mockAsyncFn = fn(async (p: boolean) => 'value'); expectType(mockFn('one', 2)); @@ -135,6 +136,8 @@ if (returnValue.type === 'throw') { expectType(returnValue.value); } +expectType>(mockFn.mock.contexts); + expectType boolean>>( mockFn.mockClear(), ); diff --git a/packages/jest-mock/src/__tests__/index.test.ts b/packages/jest-mock/src/__tests__/index.test.ts index bbfc2677d2b9..371ba0a04ea8 100644 --- a/packages/jest-mock/src/__tests__/index.test.ts +++ b/packages/jest-mock/src/__tests__/index.test.ts @@ -424,20 +424,53 @@ describe('moduleMocker', () => { expect(fn.mock.instances[1]).toBe(instance2); }); + it('tracks context objects passed to mock calls', () => { + const fn = moduleMocker.fn(); + expect(fn.mock.instances).toEqual([]); + + const ctx0 = {}; + fn.apply(ctx0, []); + expect(fn.mock.contexts[0]).toBe(ctx0); + + const ctx1 = {}; + fn.call(ctx1); + expect(fn.mock.contexts[1]).toBe(ctx1); + + const ctx2 = {}; + const bound2 = fn.bind(ctx2); + bound2(); + expect(fn.mock.contexts[2]).toBe(ctx2); + + // null context + fn.apply(null, []); + expect(fn.mock.contexts[3]).toBe(null); + fn.call(null); + expect(fn.mock.contexts[4]).toBe(null); + fn.bind(null)(); + expect(fn.mock.contexts[5]).toBe(null); + + // Unspecified context is `undefined` in strict mode (like in this test) and `window` otherwise. + fn(); + expect(fn.mock.contexts[6]).toBe(undefined); + }); + it('supports clearing mock calls', () => { const fn = moduleMocker.fn(); expect(fn.mock.calls).toEqual([]); fn(1, 2, 3); expect(fn.mock.calls).toEqual([[1, 2, 3]]); + expect(fn.mock.contexts).toEqual([undefined]); fn.mockReturnValue('abcd'); fn.mockClear(); expect(fn.mock.calls).toEqual([]); + expect(fn.mock.contexts).toEqual([]); fn('a', 'b', 'c'); expect(fn.mock.calls).toEqual([['a', 'b', 'c']]); + expect(fn.mock.contexts).toEqual([undefined]); expect(fn()).toEqual('abcd'); }); diff --git a/packages/jest-mock/src/index.ts b/packages/jest-mock/src/index.ts index 8358cbe36631..85fe4aeb229c 100644 --- a/packages/jest-mock/src/index.ts +++ b/packages/jest-mock/src/index.ts @@ -201,6 +201,10 @@ type MockFunctionState = { * List of all the object instances that have been instantiated from the mock. */ instances: Array>; + /** + * List of all the function contexts that have been applied to calls to the mock. + */ + contexts: Array>; /** * List of the call order indexes of the mock. Jest is indexing the order of * invocations of all mocks in a test file. The index is starting with `1`. @@ -569,6 +573,7 @@ export class ModuleMocker { private _defaultMockState(): MockFunctionState { return { calls: [], + contexts: [], instances: [], invocationCallOrder: [], results: [], @@ -636,6 +641,7 @@ export class ModuleMocker { const mockState = mocker._ensureMockState(f); const mockConfig = mocker._ensureMockConfig(f); mockState.instances.push(this); + mockState.contexts.push(this); mockState.calls.push(args); // Create and record an "incomplete" mock result immediately upon // calling rather than waiting for the mock to return. This avoids