Skip to content

Commit

Permalink
fix(jest-runner): allow a different rootDir
Browse files Browse the repository at this point in the history
The Jest Runner now adhers to the way Jest deremines the [rootDir](https://jestjs.io/docs/configuration#rootdir-string).
  • Loading branch information
nicojs committed Jun 14, 2021
1 parent 825548c commit b66a617
Show file tree
Hide file tree
Showing 16 changed files with 100 additions and 77 deletions.
3 changes: 2 additions & 1 deletion e2e/test/coverage-analysis/jest-spec/jest.config.json
@@ -1,4 +1,5 @@
{
"testEnvironment": "node",
"testMatch": ["**/jest-spec/*.spec.js"]
"rootDir": "..",
"testMatch": ["<rootDir>/jest-spec/*.spec.js"]
}
Expand Up @@ -51,7 +51,7 @@ export class CustomJestConfigLoader implements JestConfigLoader {
private resolvePackageJsonFilePath(): string | undefined {
const jestOptions = this.options as JestRunnerOptionsWithStrykerOptions;
const packageJsonCandidate = path.resolve(jestOptions.jest.configFile ?? 'package.json');
if (packageJsonCandidate.endsWith('.json') && (jestOptions.jest.configFile || fs.existsSync(packageJsonCandidate))) {
if (packageJsonCandidate.endsWith('package.json') && (jestOptions.jest.configFile || fs.existsSync(packageJsonCandidate))) {
return packageJsonCandidate;
}
return undefined;
Expand All @@ -63,7 +63,7 @@ export class CustomJestConfigLoader implements JestConfigLoader {
private resolveJestConfigFilePath(): string | undefined {
const jestOptions = this.options as JestRunnerOptionsWithStrykerOptions;
const configFileCandidate = path.resolve(jestOptions.jest.configFile ?? 'jest.config.js');
if (configFileCandidate.endsWith('.js') && (jestOptions.jest.configFile || fs.existsSync(configFileCandidate))) {
if (!configFileCandidate.endsWith('package.json') && (jestOptions.jest.configFile || fs.existsSync(configFileCandidate))) {
return configFileCandidate;
}
return undefined;
Expand Down
Expand Up @@ -4,7 +4,7 @@ import { JestRunResult } from '../jest-run-result';
import { JestTestAdapter, RunSettings } from './jest-test-adapter';

export class JestGreaterThan25TestAdapter implements JestTestAdapter {
public async run({ jestConfig, projectRoot, fileNameUnderTest, testNamePattern, testLocationInResults }: RunSettings): Promise<JestRunResult> {
public async run({ jestConfig, fileNameUnderTest, testNamePattern, testLocationInResults }: RunSettings): Promise<JestRunResult> {
const config = JSON.stringify(jestConfig);
const result = await jestWrapper.runCLI(
{
Expand All @@ -17,7 +17,7 @@ export class JestGreaterThan25TestAdapter implements JestTestAdapter {
testNamePattern,
testLocationInResults,
},
[projectRoot]
[jestConfig.rootDir ?? process.cwd()]
);
return result;
}
Expand Down
@@ -1,5 +1,3 @@
import { Config } from '@jest/types';

import { jestWrapper } from '../utils';
import { JestRunResult } from '../jest-run-result';

Expand All @@ -10,17 +8,20 @@ import { RunSettings, JestTestAdapter } from './jest-test-adapter';
* It has a lot of `any` typings here, since the installed typings are not in sync.
*/
export class JestLessThan25TestAdapter implements JestTestAdapter {
public run({ jestConfig, projectRoot, fileNameUnderTest, testNamePattern, testLocationInResults }: RunSettings): Promise<JestRunResult> {
public run({ jestConfig, fileNameUnderTest, testNamePattern, testLocationInResults }: RunSettings): Promise<JestRunResult> {
const config = JSON.stringify(jestConfig);
return jestWrapper.runCLI(
{
...(fileNameUnderTest && { _: [fileNameUnderTest], findRelatedTests: true }),
$0: 'stryker',
_: fileNameUnderTest ? [fileNameUnderTest] : [],
findRelatedTests: !!fileNameUnderTest,
config,
runInBand: true,
silent: true,
testNamePattern,
} as Config.Argv,
[projectRoot]
testLocationInResults,
},
[jestConfig.rootDir ?? process.cwd()]
);
}
}
Expand Up @@ -4,7 +4,6 @@ import { JestRunResult } from '../jest-run-result';

export interface RunSettings {
jestConfig: Config.InitialOptions;
projectRoot: string;
testNamePattern?: string;
fileNameUnderTest?: string;
testLocationInResults?: boolean;
Expand Down
2 changes: 0 additions & 2 deletions packages/jest-runner/src/jest-test-runner.ts
Expand Up @@ -106,7 +106,6 @@ export class JestTestRunner implements TestRunner {
try {
const { dryRunResult, jestResult } = await this.run({
jestConfig: withCoverageAnalysis(this.jestConfig, coverageAnalysis),
projectRoot: process.cwd(),
testLocationInResults: true,
});
if (dryRunResult.status === DryRunStatus.Complete && coverageAnalysis !== 'off') {
Expand Down Expand Up @@ -138,7 +137,6 @@ export class JestTestRunner implements TestRunner {
const { dryRunResult } = await this.run({
fileNameUnderTest,
jestConfig: this.configForMutantRun(fileNameUnderTest),
projectRoot: process.cwd(),
testNamePattern,
});
return toMutantRunResult(dryRunResult);
Expand Down
2 changes: 0 additions & 2 deletions packages/jest-runner/src/plugin-tokens.ts
@@ -1,5 +1,3 @@
export const projectRoot = 'projectRoot';
export const loader = 'loader';
export const resolve = 'resolve';
export const configLoader = 'configLoader';
export const processEnv = 'processEnv';
Expand Down
1 change: 0 additions & 1 deletion packages/jest-runner/src/utils/index.ts
@@ -1,4 +1,3 @@
export * from './create-react-jest-config';
export * from './verify-all-test-files-have-coverage';
export * from './merge-mutant-coverage';
export * from './jest-wrapper';
@@ -0,0 +1,27 @@
import { commonTokens } from '@stryker-mutator/api/plugin';
import { assertions, factory, testInjector } from '@stryker-mutator/test-helpers';
import { expect } from 'chai';

import { JestOptions } from '../../src-generated/jest-runner-options';
import { JestRunnerOptionsWithStrykerOptions } from '../../src/jest-runner-options-with-stryker-options';
import { jestTestRunnerFactory } from '../../src/jest-test-runner';
import { createJestOptions } from '../helpers/producers';

import { resolveTestResource } from '../helpers/resolve-test-resource';

describe('jest with config in child dir', () => {
function createSut(overrides?: Partial<JestOptions>) {
const options: JestRunnerOptionsWithStrykerOptions = factory.strykerWithPluginOptions({
jest: createJestOptions(overrides),
});
return testInjector.injector.provideValue(commonTokens.options, options).injectFunction(jestTestRunnerFactory);
}

it('should still be able perform a mutant run', async () => {
process.chdir(resolveTestResource('config-in-child-dir'));
const sut = createSut({ configFile: 'client/jest.config.js' });
const actual = await sut.dryRun(factory.dryRunOptions());
assertions.expectCompleted(actual);
expect(actual.tests).lengthOf(2);
});
});
Expand Up @@ -57,6 +57,19 @@ describe(CustomJestConfigLoader.name, () => {
expect(config).to.deep.contains({ rootDir: path.resolve(projectRoot, 'lib') });
});

it('should allow users to configure a jest.config.json file as "configFile"', () => {
// Arrange
fileExistsSyncStub.returns(true);
options.jest.configFile = path.resolve(projectRoot, 'jest.config.json');

// Act
const config = sut.loadConfig();

// Assert
expect(requireStub).calledWith(path.join(projectRoot, 'jest.config.json'));
expect(config).to.deep.contain(readConfig);
});

it('should allow users to configure a package.json file as "configFile"', () => {
// Arrange
fileExistsSyncStub
Expand Down
Expand Up @@ -11,11 +11,11 @@ describe(JestGreaterThan25TestAdapter.name, () => {
let sut: JestGreaterThan25TestAdapter;
let runCLIStub: sinon.SinonStub;

const projectRoot = '/path/to/project';
const fileNameUnderTest = '/path/to/file';
const jestConfig: Config.InitialOptions = { rootDir: projectRoot };
let jestConfig: Config.InitialOptions;

beforeEach(() => {
jestConfig = { rootDir: '/path/to/project' };
runCLIStub = sinon.stub(jestWrapper, 'runCLI');
runCLIStub.resolves({
config: jestConfig,
Expand All @@ -25,63 +25,69 @@ describe(JestGreaterThan25TestAdapter.name, () => {
sut = testInjector.injector.injectClass(JestGreaterThan25TestAdapter);
});

it('should call the runCLI method with the correct ---projectRoot', async () => {
await sut.run({ jestConfig, projectRoot });
expect(runCLIStub).calledWith(sinon.match.object, [projectRoot]);
it('should call the runCLI method with the correct --projectRoot', async () => {
await sut.run({ jestConfig });
expect(runCLIStub).calledWith(sinon.match.object, [jestConfig.rootDir!]);
});

it('should call the runCLI method with --projectRoot = cwd when no rootDir is provided', async () => {
delete jestConfig.rootDir;
await sut.run({ jestConfig });
expect(runCLIStub).calledWith(sinon.match.object, [process.cwd()]);
});

it('should call the runCLI method with the --findRelatedTests flag when provided', async () => {
await sut.run({ jestConfig, projectRoot, fileNameUnderTest });
await sut.run({ jestConfig, fileNameUnderTest });

expect(runCLIStub).calledWith(
sinon.match({
$0: 'stryker',
_: [fileNameUnderTest],
config: JSON.stringify({ rootDir: projectRoot }),
config: JSON.stringify(jestConfig),
findRelatedTests: true,
runInBand: true,
silent: true,
testNamePattern: undefined,
}),
[projectRoot]
[jestConfig.rootDir!]
);
});

it('should call the runCLI method with the --testNamePattern flag when provided', async () => {
await sut.run({ jestConfig, projectRoot, testNamePattern: 'Foo should bar' });
await sut.run({ jestConfig, testNamePattern: 'Foo should bar' });

expect(runCLIStub).calledWith(
sinon.match({
$0: 'stryker',
_: [],
config: JSON.stringify({ rootDir: projectRoot }),
config: JSON.stringify(jestConfig),
findRelatedTests: false,
runInBand: true,
silent: true,
testNamePattern: 'Foo should bar',
}),
[projectRoot]
[jestConfig.rootDir!]
);
});

it('should call the runCLI method with the --testLocationInResults flag when provided', async () => {
await sut.run({ jestConfig, projectRoot, testLocationInResults: true });
await sut.run({ jestConfig, testLocationInResults: true });

expect(runCLIStub).calledWith(
sinon.match({
testLocationInResults: true,
}),
[projectRoot]
[jestConfig.rootDir!]
);
});

it('should call the runCLI method without the --testLocationInResults flag when not', async () => {
await sut.run({ jestConfig, projectRoot, testLocationInResults: false });
expect(runCLIStub).calledWith(sinon.match({ testLocationInResults: false }), [projectRoot]);
await sut.run({ jestConfig, testLocationInResults: false });
expect(runCLIStub).calledWith(sinon.match({ testLocationInResults: false }), [jestConfig.rootDir!]);
});

it('should call the runCLI method and return the test result', async () => {
const result = await sut.run({ jestConfig, projectRoot });
const result = await sut.run({ jestConfig });

expect(result).to.deep.equal({
config: jestConfig,
Expand All @@ -90,7 +96,7 @@ describe(JestGreaterThan25TestAdapter.name, () => {
});

it('should call the runCLI method and return the test result when run with --findRelatedTests flag', async () => {
const result = await sut.run({ jestConfig, projectRoot, fileNameUnderTest });
const result = await sut.run({ jestConfig, fileNameUnderTest });

expect(result).to.deep.equal({
config: jestConfig,
Expand Down
4 changes: 1 addition & 3 deletions packages/jest-runner/test/unit/jest-test-runner.spec.ts
Expand Up @@ -112,7 +112,7 @@ describe(JestTestRunner.name, () => {
const sut = createSut();
testInjector.logger.isTraceEnabled.returns(true);
await sut.dryRun({ coverageAnalysis: 'off' });
expect(testInjector.logger.trace).calledWithMatch(/Invoking Jest with config\s.*/, sinon.match(/.*"jestConfig".*"projectRoot".*/));
expect(testInjector.logger.trace).calledWithMatch(/Invoking Jest with config\s.*/, sinon.match(/.*"jestConfig".*/));
});

it('should call the jestTestRunner run method and return a correct runResult', async () => {
Expand Down Expand Up @@ -481,7 +481,6 @@ describe(JestTestRunner.name, () => {
expect(jestTestAdapterMock.run).calledWithExactly(
sinon.match({
jestConfig: sinon.match.object,
projectRoot: sinon.match.string,
testNamePattern: undefined,
fileNameUnderTest: '.stryker-tmp/sandbox2/foo.js',
})
Expand All @@ -495,7 +494,6 @@ describe(JestTestRunner.name, () => {
expect(jestTestAdapterMock.run).calledWithExactly(
sinon.match({
jestConfig: sinon.match.object,
projectRoot: sinon.match.string,
testNamePattern: undefined,
fileNameUnderTest: undefined,
})
Expand Down

This file was deleted.

@@ -0,0 +1,7 @@
const project = "client"
const prefix = "<rootDir>/../"

module.exports = {
testEnvironment: "node",
testMatch: [prefix + project + "/**/*.test.js"],
}
@@ -0,0 +1,8 @@
function sum(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
exports.sum = sum;
exports.sub = sub;
@@ -0,0 +1,8 @@
const {sum, sub} = require('./sum');

test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
test('sub 1 - 0 to equal 1', () => {
expect(sub(1, 0)).toBe(1);
});

0 comments on commit b66a617

Please sign in to comment.