sinonjs / fake-timers

Fake setTimeout and friends (collectively known as "timers"). Useful in your JavaScript tests. Extracted from Sinon.JS
BSD 3-Clause "New" or "Revised" License
793 stars 103 forks source link

The `shouldAdvanceTime` option seems to cause Jest Test environment to clear during the test #449

Closed erichosick closed 1 year ago

erichosick commented 1 year ago

The shouldAdvanceTime option seems to cause Jest Test environment to clear during the test.

What did you expect to happen?

As expected, not calling jest.useFakeTimers(); causes the error ReferenceError: setImmediate is not defined when running something like sql'SELECT 1 AS one' using the postgres.js library.

Calling jest.useFakeTimers(); should set up fake timers usable by the Jest environment. We no longer get the error ReferenceError: setImmediate is not defined as expected.

However, since we don't provide a shouldAdvanceTime option, the default is false. Timers don't run so calling something like sql'SELECT 1 AS one' never completes as the postgres.js library relies on these timers (the test times out). This is my guess.

We expect passing a value for the option of { shouldAdvanceTime: true }, enabling the timer tick option, will cause the timers to work as expected by the library allowing the query to complete.

What actually happens

Passing shouldAdvanceTime as a config option to function install(config) causes the ReferenceError: setImmediate is not defined to throw again.

How to reproduce

I've debugged down to this section of code: https://github.com/sinonjs/fake-timers/blob/main/src/fake-timers-src.js#L1683-L1686 . When those lines of code are enabled and jest.useFakeTimers({ shouldAdvanceTime: true }); is called (or even if we hard code the value within the fake-timers code) then the error ReferenceError: setImmediate is not defined shows up.

If we comment out those lines of code, the ReferenceError: setImmediate is not defined goes away but the test eventually times out.

It looks like, when those lines of code are enabled, somehow the Jest framework ends up tearing down the environment early: causing jest.useRealTimers() to be called before the test completes.

SimenB commented 1 year ago

can you put together a reproduction? setImmediate is only available in Node env, so I assume the issue is simply that you are using JSDOM env. Hard to say without a reproduction, tho 🙂

fatso83 commented 1 year ago

Yeah, I have been looking at this for the last ten minutes and think it comes down to something related to what @SimenB says. Hard to look into without a repro.

If you look for setImmediatePresent in the code, you will see that that timer is only ever added if it is present in the environment, advancing timers or not: https://github.com/sinonjs/fake-timers/blob/main/src/fake-timers-src.js#L998.

So it might be something related to what _global refers to that causes this. Maybe _global is bound to different things due to something I cannot immediately see (related to JSDom and async behavior)?

erichosick commented 1 year ago

can you put together a reproduction? setImmediate is only available in Node env, so I assume the issue is simply that you are using JSDOM env. Hard to say without a reproduction, tho 🙂

yep. I updated the jest.config.ts and things started to work.

import type {
  Config,
} from 'jest';

const integrationTestConfig: Config = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  // testEnvironment: 'jsdom', -- results in the error
  testMatch: [
    '**/*.integration.spec.ts',
  ],
  collectCoverageFrom: [
    './packages/**/src/*.ts',
  ],
};

export default integrationTestConfig;

Thank you!

erichosick commented 1 year ago

Thank you again!