From 1c35865e1a649b030bd4e3b6391176af96d76710 Mon Sep 17 00:00:00 2001 From: Sander Koenders Date: Fri, 23 Feb 2018 12:17:05 +0100 Subject: [PATCH] feat(jest-config): override jest config (#34) Override config options like bail, code coverage and verbosity for performance reasons. --- .../src/JestConfigEditor.ts | 9 +++++++++ .../configLoaders/DefaultJestConfigLoader.ts | 18 ++++++++---------- .../src/configLoaders/JestConfiguration.ts | 13 +++++++++---- .../src/jestOverrideOptions.ts | 16 ++++++++++++++++ .../test/integration/JestConfigEditorSpec.ts | 18 +++++++++++++----- .../test/integration/StrykerJestRunnerSpec.ts | 8 ++++---- .../test/unit/JestConfigEditorSpec.ts | 16 ++++++++++++++++ .../configLoaders/DefaultConfigLoaderSpec.ts | 16 ++++++++-------- .../testResources/exampleProject/.gitignore | 1 + .../testResources/exampleProject/package.json | 6 ++++-- .../jest.config.js | 6 ++++-- .../package.json | 6 +++++- 12 files changed, 97 insertions(+), 36 deletions(-) create mode 100644 packages/stryker-jest-runner/src/jestOverrideOptions.ts create mode 100644 packages/stryker-jest-runner/testResources/exampleProject/.gitignore diff --git a/packages/stryker-jest-runner/src/JestConfigEditor.ts b/packages/stryker-jest-runner/src/JestConfigEditor.ts index 3c2f34fd62..75b32a4c68 100644 --- a/packages/stryker-jest-runner/src/JestConfigEditor.ts +++ b/packages/stryker-jest-runner/src/JestConfigEditor.ts @@ -3,6 +3,8 @@ import { Config, ConfigEditor } from 'stryker-api/config'; import JestConfigLoader from './configLoaders/JestConfigLoader'; import DefaultJestConfigLoader from './configLoaders/DefaultJestConfigLoader'; import ReactScriptsJestConfigLoader from './configLoaders/ReactScriptsJestConfigLoader'; +import JestConfiguration from './configLoaders/JestConfiguration'; +import JEST_OVERRIDE_OPTIONS from './jestOverrideOptions'; const DEFAULT_PROJECT_NAME = 'default'; @@ -16,6 +18,9 @@ export default class JestConfigEditor implements ConfigEditor { // When no config property is set load the configuration with the project type strykerConfig.jest.config = strykerConfig.jest.config || this.getConfigLoader(strykerConfig.jest.project).loadConfig(); + + // Override some of the config properties to optimise Jest for Stryker + strykerConfig.jest.config = this.overrideProperties(strykerConfig.jest.config); } private getConfigLoader(project: string): JestConfigLoader { @@ -34,4 +39,8 @@ export default class JestConfigEditor implements ConfigEditor { return configLoader; } + + private overrideProperties(config: JestConfiguration) { + return Object.assign(config, JEST_OVERRIDE_OPTIONS); + } } \ No newline at end of file diff --git a/packages/stryker-jest-runner/src/configLoaders/DefaultJestConfigLoader.ts b/packages/stryker-jest-runner/src/configLoaders/DefaultJestConfigLoader.ts index e9a327be8c..a77edfb093 100644 --- a/packages/stryker-jest-runner/src/configLoaders/DefaultJestConfigLoader.ts +++ b/packages/stryker-jest-runner/src/configLoaders/DefaultJestConfigLoader.ts @@ -17,26 +17,24 @@ export default class DefaultJestConfigLoader implements JestConfigLoader { } public loadConfig(): JestConfiguration { - let jestConfig: JestConfiguration; - - try { - jestConfig = this.readConfigFromJestConfigFile(); - } catch { - jestConfig = JSON.parse(this.readConfigFromPackageJson()).jest; - } + const jestConfig = this.readConfigFromJestConfigFile() || this.readConfigFromPackageJson(); if (!jestConfig) { - throw new Error('No Jest configuration found in your projectroot, please supply a jest.config.js or a jest config in your package.json'); + throw new Error('Could not read Jest configuration, please provide a jest.config.js file or a jest config in your package.json'); } return jestConfig; } private readConfigFromJestConfigFile() { - return this._loader(path.join(this._projectRoot, 'jest.config.js')); + try { + return this._loader(path.join(this._projectRoot, 'jest.config.js')); + } catch { /* Don't return anything (implicitly return undefined) */ } } private readConfigFromPackageJson() { - return this._fs.readFileSync(path.join(this._projectRoot, 'package.json'), 'utf8'); + try { + return JSON.parse(this._fs.readFileSync(path.join(this._projectRoot, 'package.json'), 'utf8')).jest; + } catch { /* Don't return anything (implicitly return undefined) */ } } } \ No newline at end of file diff --git a/packages/stryker-jest-runner/src/configLoaders/JestConfiguration.ts b/packages/stryker-jest-runner/src/configLoaders/JestConfiguration.ts index 0fb99f5f22..381bd94882 100644 --- a/packages/stryker-jest-runner/src/configLoaders/JestConfiguration.ts +++ b/packages/stryker-jest-runner/src/configLoaders/JestConfiguration.ts @@ -17,14 +17,17 @@ export interface HasteConfig { export default interface JestConfiguration { automock: boolean; browser: boolean; - cache: boolean; + bail: boolean; cacheDirectory: Path; - clearMocks: boolean; + collectCoverage: boolean; + collectCoverageFrom: Array; + coverageDirectory: string; coveragePathIgnorePatterns: string[]; + coverageReporters: Array; + forceCoverageMatch: Glob[]; cwd: Path; detectLeaks: boolean; displayName: Maybe; - forceCoverageMatch: Glob[]; globals: ConfigGlobals; haste: HasteConfig; moduleDirectories: string[]; @@ -57,4 +60,6 @@ export default interface JestConfiguration { transformIgnorePatterns: Glob[]; unmockedModulePathPatterns: Maybe; watchPathIgnorePatterns: string[]; -} + testResultsProcessor?: string; + verbose: boolean; +} \ No newline at end of file diff --git a/packages/stryker-jest-runner/src/jestOverrideOptions.ts b/packages/stryker-jest-runner/src/jestOverrideOptions.ts new file mode 100644 index 0000000000..e355fd88a3 --- /dev/null +++ b/packages/stryker-jest-runner/src/jestOverrideOptions.ts @@ -0,0 +1,16 @@ +const JEST_OVERRIDE_OPTIONS = Object.freeze({ + // Prevent the user from using his or her own testResultProcessor because this might + // Mess with the way Stryker gets the results + testResultsProcessor: undefined, + + // Disable code coverage, it is not used in Stryker and will only slow down the test runs + collectCoverage: false, + + // Disable verbose logging, this will only slow down Stryker test runs + verbose: false, + + // Enable bail so the process quits immediately when one of the tests fails + bail: true, +}); + +export default JEST_OVERRIDE_OPTIONS; \ No newline at end of file diff --git a/packages/stryker-jest-runner/test/integration/JestConfigEditorSpec.ts b/packages/stryker-jest-runner/test/integration/JestConfigEditorSpec.ts index bc610e92a3..06361f82af 100644 --- a/packages/stryker-jest-runner/test/integration/JestConfigEditorSpec.ts +++ b/packages/stryker-jest-runner/test/integration/JestConfigEditorSpec.ts @@ -62,7 +62,11 @@ describe('Integration JestConfigEditor', () => { 'node' ], rootDir: projectRoot, - setupTestFrameworkScriptFile: undefined + setupTestFrameworkScriptFile: undefined, + testResultsProcessor: undefined, + collectCoverage: false, + verbose: false, + bail: true }; // Parse the json back to an object in order to match @@ -76,12 +80,14 @@ describe('Integration JestConfigEditor', () => { expect(config.jest.project).to.equal('default'); expect(config.jest.config).to.deep.equal({ - collectCoverage: false, moduleFileExtensions: ['js', 'json', 'jsx', 'node'], testEnvironment: 'jest-environment-jsdom', testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', testRunner: 'jest-jasmine2', - verbose: true + testResultsProcessor: undefined, + collectCoverage: false, + verbose: false, + bail: true }); }); @@ -92,12 +98,14 @@ describe('Integration JestConfigEditor', () => { expect(config.jest.project).to.equal('default'); expect(config.jest.config).to.deep.equal({ - collectCoverage: false, moduleFileExtensions: ['js', 'json', 'jsx', 'node'], testEnvironment: 'jest-environment-jsdom', testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$', testRunner: 'jest-jasmine2', - verbose: true + testResultsProcessor: undefined, + collectCoverage: false, + verbose: false, + bail: true }); }); diff --git a/packages/stryker-jest-runner/test/integration/StrykerJestRunnerSpec.ts b/packages/stryker-jest-runner/test/integration/StrykerJestRunnerSpec.ts index a222345a48..a217bbf689 100644 --- a/packages/stryker-jest-runner/test/integration/StrykerJestRunnerSpec.ts +++ b/packages/stryker-jest-runner/test/integration/StrykerJestRunnerSpec.ts @@ -61,10 +61,10 @@ describe('Integration StrykerJestRunner', function () { expect(result.tests).to.be.an('array').that.is.not.empty; expect(result.tests[0].name).to.equal('renders without crashing'); expect(result.tests[0].status).to.equal(TestStatus.Success); - expect(result.tests[0].timeSpentMs).to.be.above(0); + expect(result.tests[0].timeSpentMs).to.be.above(-1); expect(result.tests[0].failureMessages).to.be.an('array').that.is.empty; expect(result.status).to.equal(RunStatus.Complete); - }).timeout(10000); + }); it('should run tests on the example custom project using package.json', async () => { processCwdStub.returns(getProjectRoot('exampleProject')); @@ -75,7 +75,7 @@ describe('Integration StrykerJestRunner', function () { const result = await jestTestRunner.run(); expect(result).to.have.property('tests'); - expect(result.tests).to.be.an('array').with.length(6); + expect(result.tests).to.be.an('array').with.length(testNames.length); for (let test of result.tests) { expect(testNames).to.include(test.name); @@ -96,7 +96,7 @@ describe('Integration StrykerJestRunner', function () { const result = await jestTestRunner.run(); expect(result).to.have.property('tests'); - expect(result.tests).to.be.an('array').with.length(6); + expect(result.tests).to.be.an('array').with.length(testNames.length); for (let test of result.tests) { expect(testNames).to.include(test.name); diff --git a/packages/stryker-jest-runner/test/unit/JestConfigEditorSpec.ts b/packages/stryker-jest-runner/test/unit/JestConfigEditorSpec.ts index f3caf6097f..56348de68f 100644 --- a/packages/stryker-jest-runner/test/unit/JestConfigEditorSpec.ts +++ b/packages/stryker-jest-runner/test/unit/JestConfigEditorSpec.ts @@ -4,6 +4,7 @@ import { assert, expect } from 'chai'; import JestConfigEditor from '../../src/JestConfigEditor'; import DefaultJestConfigLoader, * as defaultJestConfigLoader from '../../src/configLoaders/DefaultJestConfigLoader'; import ReactScriptsJestConfigLoader, * as reactScriptsJestConfigLoader from '../../src/configLoaders/ReactScriptsJestConfigLoader'; +import JestConfiguration from '../../src/configLoaders/JestConfiguration'; describe('JestConfigEditor', () => { let jestConfigEditor: JestConfigEditor; @@ -22,6 +23,10 @@ describe('JestConfigEditor', () => { sandbox.stub(defaultJestConfigLoader, 'default').returns(defaultConfigLoaderStub); sandbox.stub(reactScriptsJestConfigLoader, 'default').returns(reactConfigLoaderStub); + const defaultOptions: Partial = { collectCoverage : true, verbose: true, bail: false, testResultsProcessor: 'someResultProcessor' }; + defaultConfigLoaderStub.loadConfig.returns(defaultOptions); + reactConfigLoaderStub.loadConfig.returns(defaultOptions); + jestConfigEditor = new JestConfigEditor(); config = new Config(); }); @@ -43,6 +48,17 @@ describe('JestConfigEditor', () => { assert(reactConfigLoaderStub.loadConfig.calledOnce, 'ReactConfigLoader loadConfig not called'); }); + it('should override verbose, collectcoverage, testResultsProcessor and bail on all loaded configs', () => { + jestConfigEditor.edit(config); + + expect(config.jest.config).to.deep.equal({ + testResultsProcessor: undefined, + collectCoverage: false, + verbose: false, + bail: true + }); + }); + it('should return an error when an invalid project is defined', () => { const project = 'invalidProject'; config.set({ jest: { project } }); diff --git a/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts b/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts index 779dcf3377..5ae0b4fdd8 100644 --- a/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts +++ b/packages/stryker-jest-runner/test/unit/configLoaders/DefaultConfigLoaderSpec.ts @@ -38,8 +38,15 @@ describe('DefaultJestConfigLoader', () => { }); }); - it('should fallback and load the Jest configuration from the package.json when jest.config.js is not present in the project', () => { + it('should return an error when no Jest configuration is present in neither jest.config.js or package.json', () => { requireStub.throws(Error('ENOENT: no such file or directory, open jest.config.js')); + fsStub.readFileSync.returns('{ "name": "dummy", "version": "0.0.1", "description": "Dummy package.json without jest property"}'); + + expect(() => defaultConfigLoader.loadConfig()).to.throw(Error, 'Could not read Jest configuration, please provide a jest.config.js file or a jest config in your package.json'); + }); + + it('should fallback and load the Jest configuration from the package.json when jest.config.js is not present in the project', () => { + requireStub.throws(Error('ENOENT: no such file or directory, open package.json')); const config = defaultConfigLoader.loadConfig(); assert(fsStub.readFileSync.calledWith(path.join(projectRoot, 'package.json'), 'utf8'), `readFileSync not called with ${projectRoot}/package.json`); @@ -47,13 +54,6 @@ describe('DefaultJestConfigLoader', () => { exampleProperty: 'examplePackageJsonValue' }); }); - - it('should return an error when no Jest configuration is present in neither jest.config.js or package.json', () => { - requireStub.throws(Error('ENOENT: no such file or directory, open jest.config.js')); - fsStub.readFileSync.returns('{ "name": "dummy", "version": "0.0.1", "description": "Dummy package.json without jest property"}'); - - expect(() => defaultConfigLoader.loadConfig()).to.throw(Error, 'No Jest configuration found in your projectroot, please supply a jest.config.js or a jest config in your package.json'); - }); }); interface FsStub { diff --git a/packages/stryker-jest-runner/testResources/exampleProject/.gitignore b/packages/stryker-jest-runner/testResources/exampleProject/.gitignore new file mode 100644 index 0000000000..ed9f9cc128 --- /dev/null +++ b/packages/stryker-jest-runner/testResources/exampleProject/.gitignore @@ -0,0 +1 @@ +coverage \ No newline at end of file diff --git a/packages/stryker-jest-runner/testResources/exampleProject/package.json b/packages/stryker-jest-runner/testResources/exampleProject/package.json index 24a701ab4d..065fa1c6ac 100644 --- a/packages/stryker-jest-runner/testResources/exampleProject/package.json +++ b/packages/stryker-jest-runner/testResources/exampleProject/package.json @@ -4,11 +4,13 @@ "version": "0.0.0", "description": "A testResource for jest-test-runner", "jest": { - "collectCoverage": false, "moduleFileExtensions": ["js", "json", "jsx", "node"], "testEnvironment": "jest-environment-jsdom", "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$", "testRunner": "jest-jasmine2", - "verbose": true + "testResultsProcessor": "my-awesome-testResultProcessor", + "collectCoverage": true, + "verbose": true, + "bail": false } } diff --git a/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/jest.config.js b/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/jest.config.js index 2c5ca3d44b..884d73ae2f 100644 --- a/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/jest.config.js +++ b/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/jest.config.js @@ -1,8 +1,10 @@ module.exports = { - collectCoverage: false, moduleFileExtensions: ["js", "json", "jsx", "node"], testEnvironment: "jest-environment-jsdom", testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$", testRunner: "jest-jasmine2", - verbose: true + testResultsProcessor: "my-awesome-testResultProcessor", + collectCoverage: true, + verbose: true, + bail: false } \ No newline at end of file diff --git a/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/package.json b/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/package.json index 5c2b7f7b8d..37f0a1a4f4 100644 --- a/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/package.json +++ b/packages/stryker-jest-runner/testResources/exampleProjectWithExplicitJestConfig/package.json @@ -7,6 +7,10 @@ "moduleFileExtensions": [], "testEnvironment": "some-invalid-test-environment", "testRegex": "not-a-regular-expression", - "testRunner": "this-testRunner-does-not-exist" + "testRunner": "this-testRunner-does-not-exist", + "testResultProcessor": "an-invalid-testResult-processor", + "collectCoverage": true, + "verbose": true, + "bail": false } }