Skip to content

Commit

Permalink
feat(jest-runner): support Jest 25
Browse files Browse the repository at this point in the history
Add support for Jest 25, where Jest switched to named exports. Jest versions >22 are now all supported.

Fixes #1983
  • Loading branch information
dependabot-preview[bot] committed Feb 14, 2020
1 parent 18bf9b6 commit b45e872
Show file tree
Hide file tree
Showing 20 changed files with 20,542 additions and 19,934 deletions.
40,254 changes: 20,411 additions & 19,843 deletions e2e/test/jest-react/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion e2e/test/jest-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
"babel-preset-es2015-rollup": "~3.0.0",
"babel-preset-react": "~6.24.1",
"babel-preset-react-app": "~9.0.2",
"jest": "~24.9.0",
"enzyme": "~3.10.0",
"enzyme-adapter-react-16": "~1.15.1",
"jest": "^25.1.0",
"react-scripts": "~3.2.0"
},
"localDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion e2e/test/jest-react/stryker.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = function (config) {
mutate: ["src/{Alert,Badge,Breadcrumb}.js"],
maxConcurrentTestRunners: 2,
jest: {
projectType: 'react'
projectType: 'create-react-app'
}
});
};
2 changes: 1 addition & 1 deletion packages/jest-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"devDependencies": {
"@stryker-mutator/test-helpers": "^2.5.0",
"@types/semver": "~7.1.0",
"jest": "^24.8.0",
"jest": "^25.1.0",
"react": "~16.12.0",
"react-dom": "~16.12.0",
"react-scripts": "~3.3.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/jest-runner/src/JestConfigEditor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Config, ConfigEditor } from '@stryker-mutator/api/config';
import { Logger } from '@stryker-mutator/api/logging';
import jest from 'jest';
import { commonTokens, tokens } from '@stryker-mutator/api/plugin';

import CustomJestConfigLoader from './configLoaders/CustomJestConfigLoader';
Expand Down Expand Up @@ -53,7 +52,7 @@ export default class JestConfigEditor implements ConfigEditor {
}
}

private overrideProperties(config: jest.Configuration) {
private overrideProperties(config: Jest.Configuration) {
return Object.assign(config, JEST_OVERRIDE_OPTIONS);
}
}
17 changes: 7 additions & 10 deletions packages/jest-runner/src/JestTestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { StrykerOptions } from '@stryker-mutator/api/core';
import { Logger } from '@stryker-mutator/api/logging';
import { commonTokens, Injector, OptionsContext, tokens } from '@stryker-mutator/api/plugin';
import { RunOptions, RunResult, RunStatus, TestResult, TestRunner, TestStatus } from '@stryker-mutator/api/test_runner';
import jest from 'jest';

import { JEST_VERSION_TOKEN, jestTestAdapterFactory } from './jestTestAdapters';
import JestTestAdapter from './jestTestAdapters/JestTestAdapter';
Expand All @@ -20,7 +19,7 @@ export const PROCESS_ENV_TOKEN = 'PROCESS_ENV_TOKEN';
export const JEST_TEST_ADAPTER_TOKEN = 'jestTestAdapter';

export default class JestTestRunner implements TestRunner {
private readonly jestConfig: jest.Configuration;
private readonly jestConfig: Jest.Configuration;

private readonly enableFindRelatedTests: boolean;

Expand Down Expand Up @@ -65,7 +64,7 @@ export default class JestTestRunner implements TestRunner {

// Get the non-empty errorMessages from the jest RunResult, it's safe to cast to Array<string> here because we filter the empty error messages
const errorMessages = results.testResults
.map((testSuite: jest.TestResult) => testSuite.failureMessage)
.map((testSuite: Jest.TestResult) => testSuite.failureMessage)
.filter(errorMessage => errorMessage) as string[];

return {
Expand All @@ -83,7 +82,7 @@ export default class JestTestRunner implements TestRunner {
}
}

private processTestResults(suiteResults: jest.TestResult[]): TestResult[] {
private processTestResults(suiteResults: Jest.TestResult[]): TestResult[] {
const testResults: TestResult[] = [];

for (const suiteResult of suiteResults) {
Expand All @@ -100,16 +99,14 @@ export default class JestTestRunner implements TestRunner {
return testResults;
}

private determineTestResultStatus(status: string) {
private determineTestResultStatus(status: Jest.Status) {
switch (status) {
case 'passed':
return TestStatus.Success;
case 'pending':
return TestStatus.Skipped;
case 'todo':
return TestStatus.Skipped;
default:
case 'failed':
return TestStatus.Failed;
default:
return TestStatus.Skipped;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import fs = require('fs');
import path from 'path';

import jest from 'jest';

import JestConfigLoader from './JestConfigLoader';

/**
Expand All @@ -15,7 +13,7 @@ export default class CustomJestConfigLoader implements JestConfigLoader {
this._projectRoot = projectRoot;
}

public loadConfig(): jest.Configuration {
public loadConfig(): Jest.Configuration {
const jestConfig = this.readConfigFromJestConfigFile() || this.readConfigFromPackageJson() || {};
return jestConfig;
}
Expand Down
6 changes: 2 additions & 4 deletions packages/jest-runner/src/configLoaders/JestConfigLoader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import jest from 'jest';

/**
* The Configloader interface is used to load different kinds of configurations for Jest.
* The ConfigLoader interface is used to load different kinds of configurations for Jest.
* Custom ConfigLoaders should implement this interface, the ConfigEditor will then be able to use it to load a Jest configuration.
*
* ConfigLoaders are typically used for projects that do not provide their configuration via the package.json file (e.g. React).
Expand All @@ -14,5 +12,5 @@ export default interface JestConfigLoader {
*
* @return {JestConfiguration} an object containing the Jest configuration.
*/
loadConfig(): jest.Configuration;
loadConfig(): Jest.Configuration;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import path from 'path';

import jest from 'jest';

import { createReactJestConfig } from '../utils/createReactJestConfig';

import JestConfigLoader from './JestConfigLoader';
Expand All @@ -13,7 +11,7 @@ export default class ReactScriptsJestConfigLoader implements JestConfigLoader {
this.projectRoot = projectRoot;
}

public loadConfig(): jest.Configuration {
public loadConfig(): Jest.Configuration {
try {
// Get the location of react script, this is later used to generate the Jest configuration used for React projects.
const reactScriptsLocation = path.join(this.resolve('react-scripts/package.json'), '..');
Expand All @@ -37,7 +35,7 @@ export default class ReactScriptsJestConfigLoader implements JestConfigLoader {
return arg.code !== undefined;
}

private createJestConfig(reactScriptsLocation: string): jest.Configuration {
private createJestConfig(reactScriptsLocation: string): Jest.Configuration {
return createReactJestConfig((relativePath: string): string => path.join(reactScriptsLocation, relativePath), this.projectRoot, false);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as path from 'path';

import jest from 'jest';

import { createReactTsJestConfig } from '../utils/createReactJestConfig';

import JestConfigLoader from './JestConfigLoader';
Expand All @@ -13,7 +11,7 @@ export default class ReactScriptsTSJestConfigLoader implements JestConfigLoader
this.projectRoot = projectRoot;
}

public loadConfig(): jest.Configuration {
public loadConfig(): Jest.Configuration {
try {
// Get the location of react-ts script, this is later used to generate the Jest configuration used for React projects.
const reactScriptsTsLocation = path.join(this.resolve('react-scripts-ts/package.json'), '..');
Expand All @@ -37,7 +35,7 @@ export default class ReactScriptsTSJestConfigLoader implements JestConfigLoader
return arg.code !== undefined;
}

private createJestConfig(reactScriptsTsLocation: string): jest.Configuration {
private createJestConfig(reactScriptsTsLocation: string): Jest.Configuration {
return createReactTsJestConfig((relativePath: string): string => path.join(reactScriptsTsLocation, relativePath), this.projectRoot, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Logger } from '@stryker-mutator/api/logging';
import { commonTokens, tokens } from '@stryker-mutator/api/plugin';

import { jestWrapper } from '../utils/jestWrapper';

import JestTestAdapter from './JestTestAdapter';

export default class JestGreaterThan25Adapter implements JestTestAdapter {
public static inject = tokens(commonTokens.logger);
constructor(private readonly log: Logger) {}

public async run(jestConfig: Jest.Configuration, projectRoot: string, fileNameUnderTest?: string): Promise<Jest.RunResult> {
jestConfig.reporters = [];
const config = JSON.stringify(jestConfig);
this.log.trace(`Invoking Jest with config ${config}`);
if (fileNameUnderTest) {
this.log.trace(`Only running tests related to ${fileNameUnderTest}`);
}

const result = await jestWrapper.runCLI(
{
$0: 'stryker',
_: fileNameUnderTest ? [fileNameUnderTest] : [],
findRelatedTests: !!fileNameUnderTest,
config,
runInBand: true,
silent: true
},
[projectRoot]
);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import jest from 'jest';

import JestTestAdapter from './JestTestAdapter';

export default class JestPromiseTestAdapter implements JestTestAdapter {
/**
* The adapter used for 22 < Jest < 25.
* It has a lot of `any` typings here, since the installed typings are not in sync.
*/
export default class JestLessThan25TestAdapter implements JestTestAdapter {
public static inject = tokens(commonTokens.logger);
constructor(private readonly log: Logger) {}

public run(jestConfig: jest.Configuration, projectRoot: string, fileNameUnderTest?: string): Promise<jest.RunResult> {
public run(jestConfig: any, projectRoot: string, fileNameUnderTest?: string): Promise<any> {
jestConfig.reporters = [];
const config = JSON.stringify(jestConfig);
this.log.trace(`Invoking Jest with config ${config}`);
Expand All @@ -22,7 +26,7 @@ export default class JestPromiseTestAdapter implements JestTestAdapter {
config,
runInBand: true,
silent: true
},
} as any,
[projectRoot]
);
}
Expand Down
4 changes: 1 addition & 3 deletions packages/jest-runner/src/jestTestAdapters/JestTestAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import jest from 'jest';

export default interface JestTestAdapter {
run(config: object, projectRoot: string, fileNameUnderTest?: string): Promise<jest.RunResult>;
run(config: object, projectRoot: string, fileNameUnderTest?: string): Promise<Jest.RunResult>;
}
7 changes: 5 additions & 2 deletions packages/jest-runner/src/jestTestAdapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { Logger } from '@stryker-mutator/api/logging';
import { BaseContext, commonTokens, Injector, tokens } from '@stryker-mutator/api/plugin';
import semver from 'semver';

import JestPromiseAdapter from './JestPromiseTestAdapter';
import JestLessThan25TestAdapter from './JestLessThan25Adapter';
import JestTestAdapter from './JestTestAdapter';
import JestGreaterThan25TestAdapter from './JestGreaterThan25Adapter';

export const JEST_VERSION_TOKEN = 'jestVersion';

export function jestTestAdapterFactory(log: Logger, jestVersion: string, injector: Injector<BaseContext>) {
if (semver.satisfies(jestVersion, '<22.0.0')) {
log.debug('Detected Jest below 22.0.0: %s', jestVersion);
throw new Error('You need Jest version >= 22.0.0 to use Stryker');
} else if (semver.satisfies(jestVersion, '<25.0.0')) {
return injector.injectClass(JestLessThan25TestAdapter);
} else {
return injector.injectClass(JestPromiseAdapter);
return injector.injectClass(JestGreaterThan25TestAdapter);
}
}

Expand Down
6 changes: 2 additions & 4 deletions packages/jest-runner/src/utils/createReactJestConfig.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import jest from 'jest';

const resolveCreateJestConfig = (path: string, loader?: NodeRequire): Function => {
loader = loader || /* istanbul ignore next */ require;

return loader(path);
};

export function createReactJestConfig(resolve: Function, projectRoot: string, ejected: boolean, loader?: NodeRequire): jest.Configuration {
export function createReactJestConfig(resolve: Function, projectRoot: string, ejected: boolean, loader?: NodeRequire): Jest.Configuration {
return resolveCreateJestConfig('react-scripts/scripts/utils/createJestConfig', loader)(resolve, projectRoot, ejected);
}

export function createReactTsJestConfig(resolve: Function, projectRoot: string, ejected: boolean, loader?: NodeRequire): jest.Configuration {
export function createReactTsJestConfig(resolve: Function, projectRoot: string, ejected: boolean, loader?: NodeRequire): Jest.Configuration {
return resolveCreateJestConfig('react-scripts-ts/scripts/utils/createJestConfig', loader)(resolve, projectRoot, ejected);
}
12 changes: 12 additions & 0 deletions packages/jest-runner/src/utils/jestWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as jest from 'jest';

/**
* Direct stubbing on jest is no longer possible since jest > 25
*/
class JestWrapper {
public runCLI: typeof jest.runCLI = (...args) => {
return jest.runCLI(...args);
};
}

export const jestWrapper = new JestWrapper();
3 changes: 1 addition & 2 deletions packages/jest-runner/test/unit/JestConfigEditor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Config } from '@stryker-mutator/api/config';
import { testInjector } from '@stryker-mutator/test-helpers';
import { assert, expect } from 'chai';
import jest from 'jest';
import * as sinon from 'sinon';

import CustomJestConfigLoader, * as defaultJestConfigLoader from '../../src/configLoaders/CustomJestConfigLoader';
Expand All @@ -25,7 +24,7 @@ describe('JestConfigEditor', () => {
sinon.stub(reactScriptsJestConfigLoader, 'default').returns(reactScriptsJestConfigLoaderStub);
sinon.stub(reactScriptsTSJestConfigLoader, 'default').returns(reactScriptsTSJestConfigLoaderStub);

const defaultOptions: Partial<jest.Configuration> = {
const defaultOptions: Partial<Jest.Configuration> = {
collectCoverage: true,
verbose: true,
bail: false,
Expand Down
Loading

0 comments on commit b45e872

Please sign in to comment.