stryker-mutator / stryker-js

Mutation testing for JavaScript and friends
https://stryker-mutator.io
Apache License 2.0
2.61k stars 250 forks source link

Custom jest testEnvironment: Cannot use import statement outside a module #4688

Open FlinnBurgess opened 10 months ago

FlinnBurgess commented 10 months ago

Summary

I'm trying to run Stryker on our NextJS project. Our jest.config.mjs file has a custom testEnvironment parameter which points to a file containing import JSDOMEnvironment from 'jest-environment-jsdom';

When I run npx jest all the tests run without issue. When I run stryker run however, I end up with errors:

import JSDOMEnvironment from 'jest-environment-jsdom';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1178:20)
    at Module._compile (node:internal/modules/cjs/loader:1220:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Function.Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at Module.call [as require] (/Users/BurgFl/Development/shop-next/node_modules/next/src/server/require-hook.ts:70:26)
    at require (node:internal/modules/cjs/helpers:119:18)
    at loadJestEnvironment (/Users/BurgFl/Development/shop-next/node_modules/@stryker-mutator/jest-runner/src/jest-plugins/import-jest-environment.cts:7:33)
    at new jestEnvironmentGeneric (/Users/BurgFl/Development/shop-next/node_modules/@stryker-mutator/jest-runner/src/jest-plugins/jest-environment-generic.cts:8:50)
    at runTestInternal (/Users/BurgFl/Development/shop-next/node_modules/jest-runner/build/runTest.js:236:23)
    at runTest (/Users/BurgFl/Development/shop-next/node_modules/jest-runner/build/runTest.js:444:34)
09:04:10 (79882) ERROR Stryker Unexpected error occurred while running Stryker Error: Something went wrong in the initial test run
    at DryRunExecutor.validateResultCompleted (file:///Users/BurgFl/Development/shop-next/node_modules/@stryker-mutator/core/dist/src/process/3-dry-run-executor.js:75:15)
    at DryRunExecutor.executeDryRun (file:///Users/BurgFl/Development/shop-next/node_modules/@stryker-mutator/core/dist/src/process/3-dry-run-executor.js:95:14)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async WorkItem.execute (file:///Users/BurgFl/Development/shop-next/node_modules/@stryker-mutator/core/dist/src/concurrent/pool.js:32:28)

Stryker config

{
  "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
  "_comment": "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.",
  "packageManager": "npm",
  "reporters": [
    "html",
    "clear-text",
    "progress"
  ],
  "testRunner": "jest",
  "jest": {
    "configFile": "./jest.config.mjs"
  },
  "checkers": ["typescript"],
  "tsconfigFile": "tsconfig.json",
  "testRunner_comment": "Take a look at (missing 'homepage' URL in package.json) for information about the jest plugin.",
  "coverageAnalysis": "perTest"
}

Test runner config

import nextJest from 'next/jest.js';

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './'
});

/** @type {import('jest').Config} */
const customJestConfig = {
  // a testRegex for React TypeScrpt projects that have *.test.tsx files alongside components
  testRegex: '(.*)/(.*).test.(tsx|ts)$',
  // Add more setup options before each test is run:
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  // If using TypeScript with a baseUrl set to the root directory then you need the below for aliases to work
  moduleDirectories: ['node_modules', '<rootDir>/'],
  testEnvironment: './fix-jsdom-environment.ts',
  // Clear mocks between tests to avoid state leaking between tests
  // https://github.com/facebook/jest/issues/10242
  // @TODO: do we want to go further here and use resetMocks and/or
  // restoreMocks?
  clearMocks: true,
  moduleNameMapper: {
    '^@/(.*)$': ['<rootDir>/src/$1'],
    '^@root/(.*)$': ['<rootDir>/$1']
  },
  watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname']
};

export default createJestConfig(customJestConfig);

Stryker environment

@stryker-mutator/core@8.0.0
@stryker-mutator/jest-runner@8.0.0
@stryker-mutator/typescript-checker@8.0.0
jest@29.7.0

Test runner environment

jest --ci --cacheDirectory .jestcache

Your Environment

software version(s)
node v18.18.0
npm 10.1.0
Operating System MacOS

Add stryker.log

This over and over again:

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:73:18)
    at wrapSafe (node:internal/modules/cjs/loader:1178:20)
    at Module._compile (node:internal/modules/cjs/loader:1220:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Function.Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at Module.call [as require] (/Users/BurgFl/Development/shop-next/node_modules/next/src/server/require-hook.ts:70:26)
    at require (node:internal/modules/cjs/helpers:119:18)
    at loadJestEnvironment (/Users/BurgFl/Development/shop-next/node_modules/@stryker-mutator/jest-runner/src/jest-plugins/import-jest-environment.cts:7:33)
    at new jestEnvironmentGeneric (/Users/BurgFl/Development/shop-next/node_modules/@stryker-mutator/jest-runner/src/jest-plugins/jest-environment-generic.cts:8:50)
    at runTestInternal (/Users/BurgFl/Development/shop-next/node_modules/jest-runner/build/runTest.js:236:23)
    at runTest (/Users/BurgFl/Development/shop-next/node_modules/jest-runner/build/runTest.js:444:34), undefinedCannot use import statement outside a module /Users/BurgFl/Development/shop-next/fix-jsdom-environment.ts:1
import JSDOMEnvironment from 'jest-environment-jsdom';
^^^^^^
nicojs commented 3 months ago

Hi @FlinnBurgess thanks for opening this issue. Sorry it took me a while to get around to it.

I assume you've read through https://stryker-mutator.io/docs/stryker-js/jest-runner/#coverage-analysis

As you can see from the stack trace we're requiring your jest environment from import-jest-environment.cts directly. This means that we're using require(..) on a typescript file ('./fix-jsdom-environment.ts'). Since no custom loaders are registered this failed. I didn't know a test environment file as typescript was possible.

There are 2 ways I can think of to fix this issue.

  1. Rewrite your custom jest environment as a commonjs module (inside a .js file).
  2. Loading a custom ts loader. Could you try to add it yourself. For example, if you choose ts-node:
    {
      "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
      "_comment": "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.",
      "packageManager": "npm",
      "reporters": [
        "html",
        "clear-text",
        "progress"
      ],
      "testRunner": "jest",
    + "testRunnerNodeArgs": ["--require", "ts-node/register"],
      "jest": {
        "configFile": "./jest.config.mjs"
      },
      "checkers": ["typescript"],
      "tsconfigFile": "tsconfig.json",
      "testRunner_comment": "Take a look at (missing 'homepage' URL in package.json) for information about the jest plugin.",
      "coverageAnalysis": "perTest"
    }