diff --git a/CHANGELOG.md b/CHANGELOG.md index 6689292c5053..6de8beb56d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ - `[jest-runtime]` Fix `requireActual` on node_modules with mock present ([#7404](https://github.com/facebook/jest/pull/7404)) - `[jest-resolve]` Fix `isBuiltinModule` to support versions of node without `module.builtinModules` ([#7565](https://github.com/facebook/jest/pull/7565)) - `[babel-jest]` Set `cwd` to be resilient to it changing during the runtime of the tests ([#7574](https://github.com/facebook/jest/pull/7574)) +- `[jest-snapshot]` Write and read snapshots from disk even if `fs` is mocked ([#7080](https://github.com/facebook/jest/pull/7080)) ### Chore & Maintenance diff --git a/e2e/__tests__/__snapshots__/snapshot-mock-fs.test.js.snap b/e2e/__tests__/__snapshots__/snapshot-mock-fs.test.js.snap new file mode 100644 index 000000000000..0b187ca45889 --- /dev/null +++ b/e2e/__tests__/__snapshots__/snapshot-mock-fs.test.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`store snapshot even if fs is mocked 1`] = ` +"Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +Snapshots: 1 written, 1 total +Time: <> +Ran all test suites." +`; diff --git a/e2e/__tests__/snapshot-mock-fs.test.js b/e2e/__tests__/snapshot-mock-fs.test.js new file mode 100644 index 000000000000..95c0cc01ed2a --- /dev/null +++ b/e2e/__tests__/snapshot-mock-fs.test.js @@ -0,0 +1,40 @@ +/** + * 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 rimraf = require('rimraf'); +const path = require('path'); +const {extractSummary} = require('../Utils'); +const runJest = require('../runJest'); + +const DIR = path.resolve(__dirname, '../snapshot-mock-fs'); +const snapshotDir = path.resolve(DIR, '__tests__/__snapshots__'); +const snapshotFile = path.resolve(snapshotDir, 'snapshot.test.js.snap'); + +beforeEach(() => rimraf.sync(snapshotDir)); +afterAll(() => rimraf.sync(snapshotDir)); + +test('store snapshot even if fs is mocked', () => { + const {json, status, stderr} = runJest.json(DIR, ['--ci=false']); + + expect(status).toBe(0); + expect(json.numTotalTests).toBe(1); + expect(json.numPassedTests).toBe(1); + + expect(stderr).toMatch('1 snapshot written from 1 test suite.'); + expect(extractSummary(stderr).summary).toMatchSnapshot(); + + // $FlowFixMe dynamic require + const content = require(snapshotFile); + expect(content['snapshot 1']).toBe(` +Object { + "foo": "bar", +} +`); +}); diff --git a/e2e/snapshot-mock-fs/__tests__/snapshot.test.js b/e2e/snapshot-mock-fs/__tests__/snapshot.test.js new file mode 100644 index 000000000000..e65fa10456fd --- /dev/null +++ b/e2e/snapshot-mock-fs/__tests__/snapshot.test.js @@ -0,0 +1,19 @@ +/** + * 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. + * + * @emails oncall+jsinfra + */ +'use strict'; + +const fs = require('fs'); + +fs.writeFileSync = jest.fn(); + +test('snapshot', () => { + const thing = {foo: 'bar'}; + + expect(thing).toMatchSnapshot(); +}); diff --git a/e2e/snapshot-mock-fs/package.json b/e2e/snapshot-mock-fs/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/snapshot-mock-fs/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-util/src/installCommonGlobals.js b/packages/jest-util/src/installCommonGlobals.js index b84d99880bf8..5173450f0300 100644 --- a/packages/jest-util/src/installCommonGlobals.js +++ b/packages/jest-util/src/installCommonGlobals.js @@ -10,6 +10,7 @@ import type {ConfigGlobals} from 'types/Config'; import type {Global} from 'types/Global'; +import fs from 'fs'; import createProcessObject from './createProcessObject'; import deepCyclicCopy from './deepCyclicCopy'; @@ -31,6 +32,21 @@ export default function(globalObject: Global, globals: ConfigGlobals) { value: globalObject.Date.now.bind(globalObject.Date), writable: false, }, + [symbol.for('jest-native-read-file')]: { + enumerable: false, + value: fs.readFileSync.bind(fs), + writable: false, + }, + [symbol.for('jest-native-write-file')]: { + enumerable: false, + value: fs.writeFileSync.bind(fs), + writable: false, + }, + [symbol.for('jest-native-exists-file')]: { + enumerable: false, + value: fs.existsSync.bind(fs), + writable: false, + }, 'jest-symbol-do-not-touch': { enumerable: false, value: symbol, diff --git a/scripts/babel-plugin-jest-native-globals.js b/scripts/babel-plugin-jest-native-globals.js index d7cc7292e5e4..adafd4e6f91f 100644 --- a/scripts/babel-plugin-jest-native-globals.js +++ b/scripts/babel-plugin-jest-native-globals.js @@ -20,6 +20,15 @@ module.exports = ({template}) => { const nowDeclaration = template(` var jestNow = global[Symbol.for('jest-native-now')] || global.Date.now; `); + const fsReadFileDeclaration = template(` + var jestReadFile = global[Symbol.for('jest-native-read-file')] || fs.readFileSync; + `); + const fsWriteFileDeclaration = template(` + var jestWriteFile = global[Symbol.for('jest-native-write-file')] || fs.writeFileSync; + `); + const fsExistsFileDeclaration = template(` + var jestExistsFile = global[Symbol.for('jest-native-exists-file')] || fs.existsSync; + `); return { name: 'jest-native-globals', @@ -57,6 +66,56 @@ module.exports = ({template}) => { path.parentPath.replaceWithSourceString('jestNow'); } + if ( + path.node.name === 'fs' && + path.parent.property && + ['readFileSync', 'writeFileSync', 'existsSync'].includes( + path.parent.property.name + ) + ) { + if ( + !state.jestInjectedRead && + path.parent.property.name === 'readFileSync' + ) { + state.jestInjectedRead = true; + path + .findParent(p => p.isProgram()) + .unshiftContainer('body', fsReadFileDeclaration()); + path + .findParent(p => p.isProgram()) + .unshiftContainer('body', symbolDeclaration()); + + path.parentPath.replaceWithSourceString('jestReadFile'); + } + if ( + !state.jestInjectedWrite && + path.parent.property.name === 'writeFileSync' + ) { + state.jestInjectedWrite = true; + path + .findParent(p => p.isProgram()) + .unshiftContainer('body', fsWriteFileDeclaration()); + path + .findParent(p => p.isProgram()) + .unshiftContainer('body', symbolDeclaration()); + + path.parentPath.replaceWithSourceString('jestWriteFile'); + } + if ( + !state.jestInjectedExists && + path.parent.property.name === 'existsSync' + ) { + state.jestInjectedExists = true; + path + .findParent(p => p.isProgram()) + .unshiftContainer('body', fsExistsFileDeclaration()); + path + .findParent(p => p.isProgram()) + .unshiftContainer('body', symbolDeclaration()); + + path.parentPath.replaceWithSourceString('jestExistsFile'); + } + } }, }, };