diff --git a/CHANGELOG.md b/CHANGELOG.md index 36682127615e..1ab5e414fcb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * `[jest-cli]` Fix `EISDIR` when a directory is passed as an argument to `jest`. ([#5317](https://github.com/facebook/jest/pull/5317)) +* `[jest-config]` Added restoreMocks config option. + ([#5327](https://github.com/facebook/jest/pull/5327)) ## jest 22.1.0 diff --git a/docs/Configuration.md b/docs/Configuration.md index 2781e4237eb7..1e3e77e18799 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -605,6 +605,15 @@ argument: The function should either return a path to the module that should be resolved or throw an error if the module can't be found. +### `restoreMocks` [boolean] + +Default: `false` + +Automatically restore mock state between every test. Equivalent to calling +`jest.restoreAllMocks()` between each test. This will lead to any mocks having +their fake implementations removed and restores their initial +implementation. + ### `rootDir` [string] Default: The root of the directory containing your jest's [config file](#) _or_ diff --git a/docs/MockFunctionAPI.md b/docs/MockFunctionAPI.md index 0640a989a382..2600505e7d0d 100644 --- a/docs/MockFunctionAPI.md +++ b/docs/MockFunctionAPI.md @@ -96,6 +96,9 @@ Beware that `mockFn.mockRestore` only works when mock was created with `jest.spyOn`. Thus you have to take care of restoration yourself when manually assigning `jest.fn()`. +The [`restoreMocks`](configuration.html#restoremocks-boolean) configuration +option is available to restore mocks automatically between tests. + ### `mockFn.mockImplementation(fn)` Accepts a function that should be used as the implementation of the mock. The diff --git a/integration-tests/__tests__/__snapshots__/show_config.test.js.snap b/integration-tests/__tests__/__snapshots__/show_config.test.js.snap index f1eba0383106..d7b23f1cc99d 100644 --- a/integration-tests/__tests__/__snapshots__/show_config.test.js.snap +++ b/integration-tests/__tests__/__snapshots__/show_config.test.js.snap @@ -32,6 +32,7 @@ exports[`--showConfig outputs config info and exits 1`] = ` \\"name\\": \\"[md5 hash]\\", \\"resetMocks\\": false, \\"resetModules\\": false, + \\"restoreMocks\\": false, \\"rootDir\\": \\"<>\\", \\"roots\\": [ \\"<>\\" diff --git a/integration-tests/__tests__/auto_restore_mocks.test.js b/integration-tests/__tests__/auto_restore_mocks.test.js new file mode 100644 index 000000000000..f0f211dd4d9f --- /dev/null +++ b/integration-tests/__tests__/auto_restore_mocks.test.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. 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. + * + * @flow + */ +'use strict'; + +const runJest = require('../runJest'); + +test('suite with auto-restore', () => { + const result = runJest('auto-restore-mocks/with-auto-restore'); + expect(result.status).toBe(0); +}); + +test('suite without auto-restore', () => { + const result = runJest('auto-restore-mocks/without-auto-restore'); + expect(result.status).toBe(0); +}); diff --git a/integration-tests/auto-restore-mocks/with-auto-restore/__tests__/index.js b/integration-tests/auto-restore-mocks/with-auto-restore/__tests__/index.js new file mode 100644 index 000000000000..c9fd85856a9d --- /dev/null +++ b/integration-tests/auto-restore-mocks/with-auto-restore/__tests__/index.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. 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. + */ + +'use strict'; + +const TestClass = require('../'); +const localClass = new TestClass(); + +test('first test', () => { + jest.spyOn(localClass, 'test').mockImplementation(() => 'ABCD'); + expect(localClass.test()).toEqual('ABCD'); + expect(localClass.test).toHaveBeenCalledTimes(1); +}); + +test('second test', () => { + expect(localClass.test()).toEqual('12345'); + expect(localClass.test.mock).toBe(undefined); +}); diff --git a/integration-tests/auto-restore-mocks/with-auto-restore/index.js b/integration-tests/auto-restore-mocks/with-auto-restore/index.js new file mode 100644 index 000000000000..4ca14319257f --- /dev/null +++ b/integration-tests/auto-restore-mocks/with-auto-restore/index.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. 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. + */ + +module.exports = class Test { + test() { + return '12345'; + } +}; diff --git a/integration-tests/auto-restore-mocks/with-auto-restore/package.json b/integration-tests/auto-restore-mocks/with-auto-restore/package.json new file mode 100644 index 000000000000..e5571105e47e --- /dev/null +++ b/integration-tests/auto-restore-mocks/with-auto-restore/package.json @@ -0,0 +1,6 @@ +{ + "jest": { + "testEnvironment": "node", + "restoreMocks": true + } +} diff --git a/integration-tests/auto-restore-mocks/without-auto-restore/__tests__/index.js b/integration-tests/auto-restore-mocks/without-auto-restore/__tests__/index.js new file mode 100644 index 000000000000..53f5b7096d0f --- /dev/null +++ b/integration-tests/auto-restore-mocks/without-auto-restore/__tests__/index.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. 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. + */ + +'use strict'; + +const TestClass = require('../'); +const localClass = new TestClass(); + +describe('without an explicit restore', () => { + jest.spyOn(localClass, 'test').mockImplementation(() => 'ABCD'); + + test('first test', () => { + expect(localClass.test()).toEqual('ABCD'); + expect(localClass.test).toHaveBeenCalledTimes(1); + }); + + test('second test', () => { + expect(localClass.test()).toEqual('ABCD'); + expect(localClass.test).toHaveBeenCalledTimes(2); + }); +}); + +describe('with an explicit restore', () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + + test('first test', () => { + jest.spyOn(localClass, 'test').mockImplementation(() => 'ABCD'); + expect(localClass.test()).toEqual('ABCD'); + expect(localClass.test).toHaveBeenCalledTimes(1); + }); + + test('second test', () => { + expect(localClass.test()).toEqual('12345'); + expect(localClass.test.mock).toBe(undefined); + }); +}); diff --git a/integration-tests/auto-restore-mocks/without-auto-restore/index.js b/integration-tests/auto-restore-mocks/without-auto-restore/index.js new file mode 100644 index 000000000000..4ca14319257f --- /dev/null +++ b/integration-tests/auto-restore-mocks/without-auto-restore/index.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. 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. + */ + +module.exports = class Test { + test() { + return '12345'; + } +}; diff --git a/integration-tests/auto-restore-mocks/without-auto-restore/package.json b/integration-tests/auto-restore-mocks/without-auto-restore/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/integration-tests/auto-restore-mocks/without-auto-restore/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-circus/src/legacy_code_todo_rewrite/jest_adapter.js b/packages/jest-circus/src/legacy_code_todo_rewrite/jest_adapter.js index 73ff4b1785ce..6e8cd6470cbb 100644 --- a/packages/jest-circus/src/legacy_code_todo_rewrite/jest_adapter.js +++ b/packages/jest-circus/src/legacy_code_todo_rewrite/jest_adapter.js @@ -52,6 +52,10 @@ const jestAdapter = async ( runtime.resetAllMocks(); } + if (config.restoreMocks) { + runtime.restoreAllMocks(); + } + if (config.timers === 'fake') { environment.fakeTimers.useFakeTimers(); } diff --git a/packages/jest-cli/src/cli/args.js b/packages/jest-cli/src/cli/args.js index f56d513c8af3..578f6aeaa41d 100644 --- a/packages/jest-cli/src/cli/args.js +++ b/packages/jest-cli/src/cli/args.js @@ -411,6 +411,13 @@ export const options = { description: 'A JSON string which allows the use of a custom resolver.', type: 'string', }, + restoreMocks: { + default: undefined, + description: + 'Automatically restore mock state and implementation between every test. ' + + 'Equivalent to calling jest.restoreAllMocks() between each test.', + type: 'boolean', + }, rootDir: { description: 'The root directory that Jest should scan for tests and ' + diff --git a/packages/jest-config/src/defaults.js b/packages/jest-config/src/defaults.js index 3f35af5fd105..3ac8868ff79e 100644 --- a/packages/jest-config/src/defaults.js +++ b/packages/jest-config/src/defaults.js @@ -55,6 +55,7 @@ export default ({ preset: null, resetMocks: false, resetModules: false, + restoreMocks: false, runTestsByPath: false, runner: 'jest-runner', snapshotSerializers: [], diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 739b6e69bbec..c4dbf27c90c3 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -162,6 +162,7 @@ const getConfigs = ( resetMocks: options.resetMocks, resetModules: options.resetModules, resolver: options.resolver, + restoreMocks: options.restoreMocks, rootDir: options.rootDir, roots: options.roots, runner: options.runner, diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index 46e64544a586..1349360745b1 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -487,6 +487,7 @@ export default function normalize(options: InitialOptions, argv: Argv) { case 'reporters': case 'resetMocks': case 'resetModules': + case 'restoreMocks': case 'rootDir': case 'runTestsByPath': case 'silent': diff --git a/packages/jest-config/src/valid_config.js b/packages/jest-config/src/valid_config.js index c1559a7b5419..43947aee5b40 100644 --- a/packages/jest-config/src/valid_config.js +++ b/packages/jest-config/src/valid_config.js @@ -70,6 +70,7 @@ export default ({ resetMocks: false, resetModules: false, resolver: '/resolver.js', + restoreMocks: false, rootDir: '/', roots: [''], runTestsByPath: false, diff --git a/packages/jest-jasmine2/src/index.js b/packages/jest-jasmine2/src/index.js index b86720976cb6..ed12771f958d 100644 --- a/packages/jest-jasmine2/src/index.js +++ b/packages/jest-jasmine2/src/index.js @@ -89,6 +89,10 @@ async function jasmine2( environment.fakeTimers.useFakeTimers(); } } + + if (config.restoreMocks) { + runtime.restoreAllMocks(); + } }); env.addReporter(reporter); diff --git a/packages/jest-validate/src/__tests__/fixtures/jest_config.js b/packages/jest-validate/src/__tests__/fixtures/jest_config.js index 7bdd9b476170..5657f5466b5e 100644 --- a/packages/jest-validate/src/__tests__/fixtures/jest_config.js +++ b/packages/jest-validate/src/__tests__/fixtures/jest_config.js @@ -42,6 +42,7 @@ const defaultConfig = { preset: null, resetMocks: false, resetModules: false, + restoreMocks: false, roots: [''], snapshotSerializers: [], testEnvironment: 'jest-environment-jsdom', @@ -98,6 +99,7 @@ const validConfig = { preset: 'react-native', resetMocks: false, resetModules: false, + restoreMocks: false, rootDir: '/', roots: [''], setupFiles: ['/setup.js'], diff --git a/test_utils.js b/test_utils.js index 9ae8c272dd59..7349b7d33dba 100644 --- a/test_utils.js +++ b/test_utils.js @@ -85,6 +85,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = { resetMocks: false, resetModules: false, resolver: null, + restoreMocks: false, rootDir: '/test_root_dir/', roots: [], runner: 'jest-runner', diff --git a/types/Argv.js b/types/Argv.js index 0f1b7943405a..137d46ce2591 100644 --- a/types/Argv.js +++ b/types/Argv.js @@ -60,6 +60,7 @@ export type Argv = {| resetMocks: boolean, resetModules: boolean, resolver: ?string, + restoreMocks: boolean, rootDir: string, roots: Array, setupFiles: Array, diff --git a/types/Config.js b/types/Config.js index b9ee6cd422fa..e9625660445f 100644 --- a/types/Config.js +++ b/types/Config.js @@ -47,6 +47,7 @@ export type DefaultOptions = {| preset: ?string, resetMocks: boolean, resetModules: boolean, + restoreMocks: boolean, runner: string, runTestsByPath: boolean, snapshotSerializers: Array, @@ -117,6 +118,7 @@ export type InitialOptions = { resetMocks?: boolean, resetModules?: boolean, resolver?: ?Path, + restoreMocks?: boolean, rootDir: Path, roots?: Array, runner?: string, @@ -227,6 +229,7 @@ export type ProjectConfig = {| resetMocks: boolean, resetModules: boolean, resolver: ?Path, + restoreMocks: boolean, rootDir: Path, roots: Array, runner: string,