jest: runInBand + dynamic imports break in Jest 27.0.0-next.11

šŸ› Bug Report

Running jest in parallel works fine, but with runInBand enabled, all tests with dynamic imports fail with the error Test environment has been torn down

Often, if I run a single test, the test will work the first time and then error on subsequent tests. Changing the imported file will fix the test on the next run, but then fail on the subsequent ones.

To reproduce

//foo.js
export default 'hello'

//faulty.test.js
test('test1', async () => {
    const module = await import('./foo.js')
    expect(module).toBeDefined()
})

Run the above test with --runInBand --watch. Once finished, click enter to trigger another run.

Expected behavior

Test should pass on subsequent runs, but instead only the first test passes and subsequent runs break.

envinfo

Verified on: Windows, Mac, Ubuntu (Github action), WSL Node: 14 & 15 NPM: 6 & 7

  System:
    OS: Windows 10 10.0.19041
    CPU: (16) x64 AMD Ryzen 7 1700 Eight-Core Processor
  Binaries:
    Node: 14.17.0 - ~\AppData\Local\Volta\tools\image\node\14.17.0\node.EXE
    Yarn: 1.21.1 - C:\Program Files\Volta\yarn.EXE
    npm: 6.14.13 - ~\AppData\Local\Volta\tools\image\npm\6.14.13\bin\npm.CMD
  npmPackages:
    jest: ^27.0.0-next.11 => 27.0.0-next.11

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 16
  • Comments: 32 (7 by maintainers)

Commits related to this issue

Most upvoted comments

On my tests, this issue is fixed on Node >=16.11 issue.zip <- Credits to @fwh1990 , I just simplified the example removing ts and other unrequired code

I donā€™t think it has been fixed yet. I got this error with Node 17, 18 and 19.

Iā€™m also facing the same issue with --runInBand and it seems like that node.js have fixed it in version 16.11.0, see my GitHub Actions run.

Workaround is to disable runInBand in your project and set maxWorkers and maxConcurrency to value greater or equal test amount, for example in my repository I have maximum 7 tests per project that uses dynamic import, so Iā€™ve set this values to 8 (and probably thatā€™s why tests are pass on my local machine, I have 8 cores on laptop and 16 on desktop) As well Iā€™ve increased timeout and magic happened! My CI is now green, links:

@SimenB it seems like that for ESM we have to instantiate new worker per each test file to workaround this issue at least for thoose who use Node.js < 16.11.0

Going one step further: by placing console logs within jest-runtime loadEsmModule() function and jest-environment-node constructor and teardown(), I found out a mismatch in environment handling.

Basically, each test file has its own Node environment: it is configured before running tests within the file, and tore down at the end. What happens is

  1. env # 1 is created for the test file
  2. tests are run, dynamically importing files within the env
  3. env # 1 is tore down
  4. env # 2 is created for the second test file
  5. tests are run, but they are importing files within env # 1, triggering the error
  6. env # 2 is tore down

Iā€™ve tried to import all my modules dynamically, to use isolateModule() and resetModules(), but I couldnā€™t find a way to force the second file using its own environment.

Hi there. Iā€™m also experiencing this issue, and made a stable reproduction repository here

As you can see, Iā€™ve stripped down all my code to the minimum: a module (index.js) dynamically importing another one (internals.js).

As long as I had a single test file importing index.js, everything was fine. As soon as Iā€™ve added another test file on the same guy, the issue happens.

Iā€™ve lost considerable time digging it, without finding any workaround nor root cause, but my gut feeling is that the jest-node-environment is tear down, then reused. Its VMcontext is gone, preventing further dynamic imports to work.

For jest users:

Try running jest --clearCache before running your actual tests (equals to disable jestā€™s transform cache).

This may help in some situations.

If this works for you, you can turn off jestā€™s cache ability in jest.config with cache: false.

https://github.com/nodejs/node/issues/35889#issuecomment-1521334188

On my tests, this issue is fixed on Node >=16.11 issue.zip <- Credits to @fwh1990 , I just simplified the example removing ts and other unrequired code

A workaround Iā€™m using is to run each test file separately, rather than telling Jest to run everything in a dir tree. Itā€™s kludgy, but until V8 fixes this, so that Node can fix it, so that Jest can work with it, not a lot of choice if you donā€™t want to run into caching issues:

  "scripts": {
    "test": "run-s test:jest:*",
    "test:jest": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --verbose=false",
    "test:jest:models": "npm run test:jest -- test/models/model.test.js",
    "test:jest:migration": "npm run test:jest -- test/migration/migration.test.js",
    "test:jest:...": "...",
    "...": "...",
  },
  "devDependencies": {
    "cross-env": "^7.0.3",
    "jest": "^27.2.2",
    "npm-run-all": "^4.1.5",
    "...": "...",
  }

With testing code that relies on dynamic imports:

import { Models } from "../../index.js";
import { User } from "./user.model.js";

describe(`Testing User model`, () => {

  beforeAll(async () => {
    await Models.useDefaultStore(`./data-store`); // this falls through to a dynamic import
    Models.register(User);
  });

  ...
});

with the operative dynamic import in Models:

static async useDefaultStore(path) {
  const { FileSystemStore } = await import("./store/filesystem-store.js");
  Models.setStore(new FileSystemStore(path));
}

But even then, this workaround probably still has pitfalls that Iā€™ve simply not run into (yet).

Again, this is a bug in V8, there is nothing we can do except wait for Google to fix and release it, then Node to update their version of V8 and backport it to v12, v14 and v16