A plugin to use the jest test runner and framework in Stryker, the JavaScript mutation testing framework
npx @tessl/cli install tessl/npm-stryker-mutator--jest-runner@9.1.0StrykerJS Jest Runner is a plugin that enables the use of Jest as a test runner and framework within Stryker, the JavaScript mutation testing framework. It provides seamless integration between Jest and Stryker, supporting advanced features like per-test coverage analysis, multiple Jest environments, and optimized test execution for mutation testing workflows.
npm install @stryker-mutator/jest-runner@stryker-mutator/core ~9.1.0import { strykerPlugins, strykerValidationSchema, mixinJestEnvironment } from "@stryker-mutator/jest-runner";For Jest environment integration:
// These are pre-enhanced Jest environments (CommonJS exports)
// Node.js environment with Stryker integration
const JestEnvironmentNode = require("@stryker-mutator/jest-runner/jest-env/node");
// JSDOM environment with Stryker integration
const JestEnvironmentJsdom = require("@stryker-mutator/jest-runner/jest-env/jsdom");
// JSDOM v16 environment with Stryker integration
const JestEnvironmentJsdomSixteen = require("@stryker-mutator/jest-runner/jest-env/jsdom-sixteen");// stryker.conf.js
module.exports = {
testRunner: "jest",
coverageAnalysis: "perTest", // or "all" or "off"
jest: {
projectType: "custom", // or "create-react-app"
enableFindRelatedTests: true,
config: {
// Custom Jest configuration
testEnvironment: "node"
}
}
};import { mixinJestEnvironment } from "@stryker-mutator/jest-runner";
import { TestEnvironment } from "@jest/environment";
// Enhance Jest environment with Stryker integration
const StrykerJestEnvironment = mixinJestEnvironment(TestEnvironment);The Jest Runner is built around several key components:
Core plugin registration and validation schema for Stryker integration. Provides the main entry point for using Jest as a test runner in Stryker mutation testing.
export const strykerPlugins: Array<PluginDeclaration>;
export const strykerValidationSchema: JestRunnerOptionsSchema;Main test runner implementation that executes Jest tests within Stryker's mutation testing workflow. Handles dry runs for coverage analysis and mutant runs for testing specific mutations.
export class JestTestRunner implements TestRunner {
init(): Promise<void>;
capabilities(): TestRunnerCapabilities;
dryRun(options: Pick<DryRunOptions, 'coverageAnalysis' | 'files'>): Promise<DryRunResult>;
mutantRun(options: MutantRunOptions): Promise<MutantRunResult>;
}
export function createJestTestRunnerFactory(
namespace?: typeof INSTRUMENTER_CONSTANTS.NAMESPACE | '__stryker2__'
): {
(injector: Injector<PluginContext>): JestTestRunner;
inject: ['$injector'];
};
export const jestTestRunnerFactory: ReturnType<typeof createJestTestRunnerFactory>;Jest environment enhancement utilities that provide Stryker-specific functionality including instrumenter context sharing and test event handling for coverage analysis.
export function mixinJestEnvironment<T extends typeof JestEnvironment>(
JestEnvironmentClass: T & { [STRYKER_JEST_ENV]?: true }
): T;Configuration loading and validation for different Jest project types including custom projects and Create React App setups. Handles Jest config merging and validation.
export interface JestOptions {
projectType: JestProjectType;
configFile?: string;
config?: { [k: string]: unknown };
enableFindRelatedTests: boolean;
}
export type JestProjectType = 'create-react-app' | 'custom';
export interface JestRunnerOptions {
jest: JestOptions;
[k: string]: unknown;
}
export interface JestRunnerOptionsWithStrykerOptions extends StrykerOptions, JestRunnerOptions {}Core utility functions and wrapper classes for Jest integration, version handling, and coverage validation.
export class JestWrapper {
getVersion(): string;
}
export class JestConfigWrapper {
// Configuration loading and manipulation utilities
}
export function verifyAllTestFilesHaveCoverage(
jestResult: AggregatedResult,
testFilesWithStrykerEnvironment: Set<string>
): string | undefined;
export function withCoverageAnalysis(
jestConfig: Config.InitialOptions,
coverageAnalysis: CoverageAnalysis,
jestWrapper: JestWrapper
): Config.InitialOptions;
export function withHitLimit(
jestConfig: Config.InitialOptions,
hitLimit: number | undefined,
jestWrapper: JestWrapper
): Config.InitialOptions;
export const JEST_OVERRIDE_OPTIONS: Readonly<Config.InitialOptions>;
export const pluginTokens: {
readonly requireFromCwd: 'requireFromCwd';
readonly resolve: 'resolve';
readonly resolveFromDirectory: 'resolveFromDirectory';
readonly configLoader: 'configLoader';
readonly processEnv: 'processEnv';
readonly jestTestAdapter: 'jestTestAdapter';
readonly globalNamespace: 'globalNamespace';
readonly jestWrapper: 'jestWrapper';
readonly jestConfigWrapper: 'jestConfigWrapper';
};interface PluginDeclaration {
kind: PluginKind;
name: string;
factory: Function;
}
interface TestRunnerCapabilities {
reloadEnvironment: boolean;
}interface DryRunOptions {
coverageAnalysis: CoverageAnalysis;
files: string[];
}
interface MutantRunOptions {
activeMutant: Mutant;
sandboxFileName: string;
testFilter?: string[];
disableBail: boolean;
hitLimit?: number;
}
interface DryRunResult {
status: DryRunStatus;
tests?: TestResult[];
errorMessage?: string;
mutantCoverage?: MutantCoverage;
}
interface MutantRunResult {
status: MutantRunStatus;
tests?: TestResult[];
errorMessage?: string;
killedBy?: string[];
survivalReason?: string;
}interface RunSettings {
jestConfig: Config.InitialOptions;
testNamePattern?: string;
fileNamesUnderTest?: string[];
testLocationInResults?: boolean;
}
interface JestRunResult {
results: AggregatedResult;
globalConfig: Config.GlobalConfig;
}
interface JestTestAdapter {
run(settings: RunSettings): Promise<JestRunResult>;
}// Jest configuration types (from @jest/types)
namespace Config {
interface InitialOptions {
[key: string]: unknown;
testEnvironment?: string;
collectCoverageFrom?: string[];
coverageReporters?: string[];
setupFilesAfterEnv?: string[];
transform?: { [key: string]: string };
testMatch?: string[];
testPathIgnorePatterns?: string[];
moduleNameMapping?: { [key: string]: string };
}
interface GlobalConfig extends InitialOptions {
projects: ProjectConfig[];
watch: boolean;
watchman: boolean;
}
interface ProjectConfig extends InitialOptions {
displayName?: string;
rootDir: string;
testMatch: string[];
}
}
// Jest test result types (from @jest/test-result)
interface AggregatedResult {
numFailedTests: number;
numPassedTests: number;
numPendingTests: number;
numTotalTests: number;
success: boolean;
testResults: TestResult[];
}
// Stryker core types
type CoverageAnalysis = 'off' | 'all' | 'perTest';
interface Mutant {
id: string;
fileName: string;
mutatorName: string;
replacement: string;
range: [number, number];
}
enum DryRunStatus {
Complete = 'Complete',
Error = 'Error',
Timeout = 'Timeout'
}
enum MutantRunStatus {
Killed = 'Killed',
Survived = 'Survived',
Timeout = 'Timeout',
Error = 'Error'
}
interface TestResult {
id: string;
name: string;
status: TestStatus;
timeSpentMs?: number;
failureMessage?: string;
}
enum TestStatus {
Success = 'Success',
Failed = 'Failed',
Skipped = 'Skipped'
}
// Stryker constants
const INSTRUMENTER_CONSTANTS = {
NAMESPACE: '__stryker__'
} as const;