Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Jest fails wrong test case #14068

Closed
linhx opened this issue Apr 13, 2023 · 13 comments
Closed

[Bug]: Jest fails wrong test case #14068

linhx opened this issue Apr 13, 2023 · 13 comments

Comments

@linhx
Copy link

linhx commented Apr 13, 2023

Version

26.0.1

Steps to reproduce

Example steps:

  1. Clone my repo at https://github.com/linhx/jest-import-after-torn-down
  2. npm install
  3. npm run test
  4. You should see the error come up

Or you can check the result here https://github.com/linhx/jest-import-after-torn-down/actions/runs/4686813490/jobs/8305329437

Expected behavior

no test cases failed

Actual behavior

Test case of src/test/bnothertest.spec.ts was failed

Additional context

I have a Nestjs project which used quite a lot of dependencies. Some of the dependencies import a module and try to use that module after the Jest environment has been torn down. An error occurs:

TypeError: Right-hand side of 'instanceof' is not callable

That causes one of the test case failed, even that test case should not be failed.

I reproduce the issue by create this. In the src/test/app.controller.spec.ts, I import a module which has an dynamic import and use a module with a timeout to make the test imports a module after Jest env torn down.
I set the timeout so that it will run while src/test/bnothertest.spec.ts is running. When the error occurs, jest fails src/test/bnothertest.spec.ts.

 PASS  src/test/app.controller.spec.ts

ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.

      1 | const dynamicImport = () => {
      2 |   setTimeout(async () => {
    > 3 |     const otherModule: any = await import('./other-module');
        |                                                                ^
      4 |     const x: any = function () {}
      5 |     x instanceof otherModule;
      6 |   }, 6000)

      at test-async-exec.ts:3:64
      at Timeout._onTimeout (test-async-exec.ts:3:29)
 FAIL  src/test/bnothertest.spec.ts
  ● AppController › root › should return "Hello World!"

    TypeError: Right-hand side of 'instanceof' is not callable

      3 |     const otherModule: any = await import('./other-module');
      4 |     const x: any = function () {}
    > 5 |     x instanceof otherModule;
        |           ^
      6 |   }, 6000)
      7 | }
      8 |

      at Timeout._onTimeout (test-async-exec.ts:5:11)

Test Suites: 1 failed, 1 passed, 2 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        7.798 s

This error happens if an error throw from a timer of previous test, not about dynamic import. E.g. this causes the error too:

// test-async-exec.ts
const dynamicImport = () => {
  setTimeout(async () => {
    throw new Error('An error');
  }, 6000);
};

dynamicImport();

The result:

 PASS  src/test/app.controller.spec.ts
 FAIL  src/test/bnothertest.spec.ts
  ● AppController › root › should return "Hello World!"

    An error

      1 | const dynamicImport = () => {
      2 |   setTimeout(async () => {
    > 3 |     throw new Error('An error');
        |               ^
      4 |   }, 6000);
      5 | };
      6 |

      at Timeout._onTimeout (test-async-exec.ts:3:15)

Test Suites: 1 failed, 1 passed, 2 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        7.855 s

Environment

System:
  OS: Ubuntu 22.04
  CPU: (8) amd64
Binaries:
  Node: 14.21.3, 16.19.1
  npm: 6.14.18, 8.19.3
npmPackages:
  jest: 26.0.1
@mrazauskas
Copy link
Contributor

mrazauskas commented Apr 13, 2023

Did you try upgrading Jest? v26 is rather old. It can be that it simply does not support dynamic import().

I can’t reproduce the issue after upgrading jest and ts-jest to the latest versions.


By the way, reproduction repo has few "unmet peer dependency" errors including:

"ts-jest@26.1.0" has incorrect peer dependency "typescript@>=3.8 <4.0"

@linhx
Copy link
Author

linhx commented Apr 13, 2023

@mrazauskas I've just updated Jest to v29.5.0 (by create new project with nestjs v9.4.0): commit
But still got the error https://github.com/linhx/jest-import-after-torn-down/actions/runs/4688421117/jobs/8308913280

@mrazauskas
Copy link
Contributor

I took a second look. First time I was running tests with npx jest. In that case the tests are passing despite errors as you expect.

Seems like with --runInBand timings become unfortunate. (It is all about timing, as you noticed while reproducing the error). First test file is finishing fast and it gets marked as passed. A timeout is left and it errors while second test file is running. The error is caught and assigned to currently running test (it can be any test running at that moment). Since there was an error, that test file is marked as failed.

Might be I miss something, but it sounds like this is hardly possible to be improved.

@linhx
Copy link
Author

linhx commented Apr 14, 2023

@mrazauskas thank you for the response.

@linhx
Copy link
Author

linhx commented Apr 17, 2023

For now, I've resolved this by store all the timers. and clear it after each test case

// timers.ts
const _setInterval = global.setInterval;
const _setTimeout = global.setTimeout;
const _setImmediate = global.setImmediate;

const INTERVALS: (NodeJS.Timeout & number)[] = [];
const TIMEOUTS: (NodeJS.Timeout & number)[] = [];
const IMMEDIATES: (NodeJS.Timeout & number)[] = [];

global.setInterval = function (): NodeJS.Timeout {
  const interval = _setInterval(...arguments);
  INTERVALS.push(interval);
  return interval;
};

/**
 * didn't work with promisify(setTimeout)
 * @returns 
 */
global.setTimeout = function (): NodeJS.Timeout {
  const timeout = _setTimeout(...arguments);
  TIMEOUTS.push(timeout);
  return timeout;
};

global.setImmediate = function (): NodeJS.Immediate {
  const immediate = _setImmediate(...arguments);
  IMMEDIATES.push(immediate);
  return immediate;
};

export const clearAllTimers = () => {
  INTERVALS.forEach((i) => clearInterval(i));
  TIMEOUTS.forEach((i) => clearTimeout(i));
  IMMEDIATES.forEach((i) => clearImmediate(i));
};

setupFilesAfterEnv file

import { clearAllTimers } from './timers';

afterAll(() => {
  clearAllTimers();
});

@mrazauskas
Copy link
Contributor

This could work. I though your problem was related with dynamic import.

@linhx
Copy link
Author

linhx commented Apr 17, 2023

It was related to dynamic import. But the root cause is throwing an error on a callback of Asynchronous resource (specifically here is Timer of setTimeout) after a test file has been torn down while another test file is running.
In the example, x instanceof otherModule; is the one causes the error TypeError: Right-hand side of 'instanceof' is not callable. Because dynamic import after teardown returns undefined.

@mrazauskas
Copy link
Contributor

By the way, jest.clearAllTimers() can be used instead. You could run the following code in setupFilesAfterEnv:

jest.useFakeTimers({ advanceTimers: true });

afterAll(() => {
  jest.clearAllTimers();
});

The advanceTimers: true option allows Jest to control timer APIs without freezing the time. In other words, this makes jest.clearAllTimers() work without any side effects (e.g. there will be no need to advance the clock manually).

@linhx
Copy link
Author

linhx commented Apr 20, 2023

Work like a charm. Thank you

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label May 24, 2023
@github-actions
Copy link

This issue was closed because it has been stalled for 30 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 23, 2023
@github-actions
Copy link

This issue was closed because it has been stalled for 30 days with no activity. Please open a new issue if the issue is still relevant, linking to this one.

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants