From 60e29fefccb093b9e5a78f16eb04dc556e3dc0e4 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sun, 30 Sep 2018 21:54:19 +0200 Subject: [PATCH] fix: write snapshots to disk even if FS is mocked Fixes #5270 --- .../snapshot-mock-fs.test.js.snap | 9 ++++ e2e/__tests__/snapshot-mock-fs.test.js | 40 +++++++++++++++++ .../__tests__/snapshot.test.js | 19 ++++++++ e2e/snapshot-mock-fs/package.json | 5 +++ .../jest-snapshot/src/__tests__/utils.test.js | 44 +++++++++---------- packages/jest-snapshot/src/bound_fs.js | 16 +++++++ packages/jest-snapshot/src/utils.js | 9 ++-- 7 files changed, 116 insertions(+), 26 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/snapshot-mock-fs.test.js.snap create mode 100644 e2e/__tests__/snapshot-mock-fs.test.js create mode 100644 e2e/snapshot-mock-fs/__tests__/snapshot.test.js create mode 100644 e2e/snapshot-mock-fs/package.json create mode 100644 packages/jest-snapshot/src/bound_fs.js 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-snapshot/src/__tests__/utils.test.js b/packages/jest-snapshot/src/__tests__/utils.test.js index ea6d2191f430..46b2c44afea0 100644 --- a/packages/jest-snapshot/src/__tests__/utils.test.js +++ b/packages/jest-snapshot/src/__tests__/utils.test.js @@ -5,11 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -jest.mock('fs'); +jest.mock('../bound_fs', () => ({ + boundExistsSync: jest.fn(() => true), + boundReadFile: jest.fn(), + boundWriteFile: jest.fn(), +})); -const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); +const boundFs = require('../bound_fs'); const { getSnapshotData, @@ -23,18 +27,8 @@ const { SNAPSHOT_VERSION_WARNING, } = require('../utils'); -const writeFileSync = fs.writeFileSync; -const readFileSync = fs.readFileSync; -const existsSync = fs.existsSync; beforeEach(() => { - fs.writeFileSync = jest.fn(); - fs.readFileSync = jest.fn(); - fs.existsSync = jest.fn(() => true); -}); -afterEach(() => { - fs.writeFileSync = writeFileSync; - fs.readFileSync = readFileSync; - fs.existsSync = existsSync; + jest.clearAllMocks(); }); test('keyToTestName()', () => { @@ -57,7 +51,7 @@ test('saveSnapshotFile() works with \r\n', () => { }; saveSnapshotFile(data, filename); - expect(fs.writeFileSync).toBeCalledWith( + expect(boundFs.boundWriteFile).toBeCalledWith( filename, `// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` + 'exports[`myKey`] = `
\n
`;\n', @@ -71,7 +65,7 @@ test('saveSnapshotFile() works with \r', () => { }; saveSnapshotFile(data, filename); - expect(fs.writeFileSync).toBeCalledWith( + expect(boundFs.boundWriteFile).toBeCalledWith( filename, `// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` + 'exports[`myKey`] = `
\n
`;\n', @@ -80,7 +74,9 @@ test('saveSnapshotFile() works with \r', () => { test('getSnapshotData() throws when no snapshot version', () => { const filename = path.join(__dirname, 'old-snapshot.snap'); - fs.readFileSync = jest.fn(() => 'exports[`myKey`] = `
\n
`;\n'); + boundFs.boundReadFile.mockImplementation( + () => 'exports[`myKey`] = `
\n
`;\n', + ); const update = 'none'; expect(() => getSnapshotData(filename, update)).toThrowError( @@ -95,7 +91,7 @@ test('getSnapshotData() throws when no snapshot version', () => { test('getSnapshotData() throws for older snapshot version', () => { const filename = path.join(__dirname, 'old-snapshot.snap'); - fs.readFileSync = jest.fn( + boundFs.boundReadFile.mockImplementation( () => `// Jest Snapshot v0.99, ${SNAPSHOT_GUIDE_LINK}\n\n` + 'exports[`myKey`] = `
\n
`;\n', @@ -118,7 +114,7 @@ test('getSnapshotData() throws for older snapshot version', () => { test('getSnapshotData() throws for newer snapshot version', () => { const filename = path.join(__dirname, 'old-snapshot.snap'); - fs.readFileSync = jest.fn( + boundFs.boundReadFile.mockImplementation( () => `// Jest Snapshot v2, ${SNAPSHOT_GUIDE_LINK}\n\n` + 'exports[`myKey`] = `
\n
`;\n', @@ -141,7 +137,9 @@ test('getSnapshotData() throws for newer snapshot version', () => { test('getSnapshotData() does not throw for when updating', () => { const filename = path.join(__dirname, 'old-snapshot.snap'); - fs.readFileSync = jest.fn(() => 'exports[`myKey`] = `
\n
`;\n'); + boundFs.boundReadFile.mockImplementation( + () => 'exports[`myKey`] = `
\n
`;\n', + ); const update = 'all'; expect(() => getSnapshotData(filename, update)).not.toThrow(); @@ -149,7 +147,9 @@ test('getSnapshotData() does not throw for when updating', () => { test('getSnapshotData() marks invalid snapshot dirty when updating', () => { const filename = path.join(__dirname, 'old-snapshot.snap'); - fs.readFileSync = jest.fn(() => 'exports[`myKey`] = `
\n
`;\n'); + boundFs.boundReadFile.mockImplementation( + () => 'exports[`myKey`] = `
\n
`;\n', + ); const update = 'all'; expect(getSnapshotData(filename, update)).toMatchObject({dirty: true}); @@ -157,7 +157,7 @@ test('getSnapshotData() marks invalid snapshot dirty when updating', () => { test('getSnapshotData() marks valid snapshot not dirty when updating', () => { const filename = path.join(__dirname, 'old-snapshot.snap'); - fs.readFileSync = jest.fn( + boundFs.boundReadFile.mockImplementation( () => `// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}\n\n` + 'exports[`myKey`] = `
\n
`;\n', @@ -171,7 +171,7 @@ test('escaping', () => { const filename = path.join(__dirname, 'escaping.snap'); const data = '"\'\\'; saveSnapshotFile({key: data}, filename); - const writtenData = fs.writeFileSync.mock.calls[0][1]; + const writtenData = boundFs.boundWriteFile.mock.calls[0][1]; expect(writtenData).toBe( `// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` + 'exports[`key`] = `"\'\\\\`;\n', diff --git a/packages/jest-snapshot/src/bound_fs.js b/packages/jest-snapshot/src/bound_fs.js new file mode 100644 index 000000000000..b45c9b3bdb93 --- /dev/null +++ b/packages/jest-snapshot/src/bound_fs.js @@ -0,0 +1,16 @@ +/** + * 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 + */ + +// This file exists so that mocks in userland does not affect snapshots + +import fs from 'fs'; + +export const boundReadFile = fs.readFileSync.bind(fs); +export const boundWriteFile = fs.writeFileSync.bind(fs); +export const boundExistsSync = fs.existsSync.bind(fs); diff --git a/packages/jest-snapshot/src/utils.js b/packages/jest-snapshot/src/utils.js index 13f7b76a4465..a97cc5dab9d8 100644 --- a/packages/jest-snapshot/src/utils.js +++ b/packages/jest-snapshot/src/utils.js @@ -11,12 +11,13 @@ import type {Path, SnapshotUpdateState} from 'types/Config'; import {getSerializers} from './plugins'; import chalk from 'chalk'; -import fs from 'fs'; import mkdirp from 'mkdirp'; import naturalCompare from 'natural-compare'; import path from 'path'; import prettyFormat from 'pretty-format'; +import {boundReadFile, boundWriteFile, boundExistsSync} from './bound_fs'; + export const SNAPSHOT_VERSION = '1'; const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/; export const SNAPSHOT_GUIDE_LINK = 'https://goo.gl/fbAQLP'; @@ -99,9 +100,9 @@ export const getSnapshotData = ( let snapshotContents = ''; let dirty = false; - if (fs.existsSync(snapshotPath)) { + if (boundExistsSync(snapshotPath)) { try { - snapshotContents = fs.readFileSync(snapshotPath, 'utf8'); + snapshotContents = boundReadFile(snapshotPath, 'utf8'); // eslint-disable-next-line no-new-func const populate = new Function('exports', snapshotContents); // $FlowFixMe @@ -172,7 +173,7 @@ export const saveSnapshotFile = ( ); ensureDirectoryExists(snapshotPath); - fs.writeFileSync( + boundWriteFile( snapshotPath, writeSnapshotVersion() + '\n\n' + snapshots.join('\n\n') + '\n', );