jest icon indicating copy to clipboard operation
jest copied to clipboard

Provide a way to find out whether fake timers have been enabled.

Open 9still opened this issue 5 years ago • 13 comments

🚀 Feature Proposal

Provide a method (e.g. jest.usingFakeTimers()) that returns true if jest.useFakeTimers was previously invoked. Alternatively, a property or a config object could be exposed with the same information.

Motivation

It is often convenient to have a global cleanup routine for tests that does things like always reverting to real timers, etc. It would be awesome if we could flush timers (e.g. jest.runOnlyPendingTimers()) in this cleanup routine as well, but to do that, one would need to find out if fake timers are actually being used.

There are already examples of libraries resorting to introspection of the setTimeout implementation to try to deduce this for similar purposes.

@kentcdodds had to do the following https://github.com/testing-library/react-testing-library/pull/720/files recently

function getIsUsingFakeTimers() {
  return (
    typeof jest !== 'undefined' &&
    typeof setTimeout !== 'undefined' &&
    (setTimeout.hasOwnProperty('_isMockFunction') ||
      setTimeout.hasOwnProperty('clock'))
  )
}

Would be awesome if jest exposed an official method to check the above, so that tooling wouldn't break if the underlying implementation were to change.

Example

afterEach(() => {
  if (jest.usingFakeTimers()) {
    jest.runOnlyPendingTimers();
  }
});

Pitch

Why does this feature belong in the Jest core platform?

Since jest.useFakeTimers is a part of the core platform, it seems reasonable to be able to find out whether that call had previously been invoked. The core platform is the only place that would be able to provide this information.

9still avatar Sep 24 '20 23:09 9still

I think something similar can be achieved by doing the following:

afterEach(() => {
  if (jest.isMockFunction(setTimeout)) {
    jest.runOnlyPendingTimers();
    jest.useRealTimers();
  }
});

https://jestjs.io/docs/jest-object#jestismockfunctionfn

cschwebk avatar Oct 20 '21 23:10 cschwebk

I do exactly this: https://github.com/kentcdodds/bookshelf/blob/d4851afda9ae40feb61ca28f76e957290ed2bfab/src/setupTests.js#L52-L55

kentcdodds avatar Oct 20 '21 23:10 kentcdodds

Thanks for the suggestion @cschwebk ! That seems like a very clean workaround, but it'd be awesome if it was officially supported/documented, since strictly speaking without that, it's not guaranteed that enabling fake timers will make setTimeout an actual mock function and could thus break at any time if the underlying implementation changes.

9still avatar Oct 20 '21 23:10 9still

It's worth noting that these methods aren't actually correct. If you use jest.spyOn(global, "setTimeout"), the checks above will still be true despite the fact that timers aren't actually using the fake implementations. So simply checking if setTimeout is mocked isn't enough to definitively determine.

I was not able to find any way to differentiate between a spy and a mock so I'm not sure how it'd be possible to determine this reliably, even in a hacky way.

If only there was a property/method exposed by Jest which could help us! 😉

CreativeTechGuy avatar Apr 05 '22 03:04 CreativeTechGuy

This seems not to work with jest 28.1.0 - jest.isMockFunction(setTimeout) will always return false, regardless of using real or fake timers.

As a temporary and hacky workaround that is almost certain to break, checking the setTimeout.name property seems to be an indication of whether the timers are mocked, but this will be extremely brittle long term. When the timers are mocked, setTimeout.name === "setTimeout", and when using real timers, setTimeout.name === undefined.

I can't explain why this is the case (maybe some setup from jest-environment-jsdom?), as setTimeout.name === "setTimeout" in browsers as well as node, but it seems to work for the time being.

To maybe provide some reasoning for why this feature is useful, it can be used with the react-testing-library user-event companion library. Setup of this is something like:

userEvent.setup();

But when using mocked timers, you must provide an advanceTimers function to hook into jest

userEvent.setup({ advanceTimers: jest.advanceTimersByTime });

To simplify this, it is often put in a setup wrapper, so should be able to dynamically set the correct advanceTimers function based on whether or not the running test context has mocked time.

mdtusz avatar May 24 '22 21:05 mdtusz

I can confirm that jest.isMockFunction(setTimeout) doesn't work in Jest 29 either, always returns false even if fake timers enabled :( I ended up doing the following.

if (typeof jest !== 'undefined' && setTimeout.clock != null && typeof setTimeout.clock.Date === 'function') {
  // ...
}

Can you please consider deprecating useFakeTimers() ?

vkarpov15 avatar Oct 05 '22 15:10 vkarpov15

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

github-actions[bot] avatar Oct 05 '23 16:10 github-actions[bot]

Not stale

CreativeTechGuy avatar Oct 05 '23 21:10 CreativeTechGuy

[email protected] use @sinonjs/fake-timers internally to fake timers, they detect fake timers by checking global.Date.isFake, i guess it safe for now to do the same, to check for fake timers.

https://github.com/sinonjs/fake-timers/blob/0f2861015f3fab85d36367281395ee94a7eac4fe/src/fake-timers-src.js#L1731

declare global {
    interface DateConstructor {
        /* Jest uses @sinonjs/fake-timers, that add this flag */
        isFake: boolean;
    }
}

const hasFakeTimers = global.Date.isFake === true;

ogonkov avatar Nov 21 '23 09:11 ogonkov

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

github-actions[bot] avatar Nov 20 '24 10:11 github-actions[bot]

Bump. Please deprecate useFakeTimers().

vkarpov15 avatar Nov 20 '24 16:11 vkarpov15

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

github-actions[bot] avatar Nov 20 '25 17:11 github-actions[bot]

As far as I know, this is still present as an issue. Maintainers can likely confirm faster than I to verify.

mdtusz avatar Nov 20 '25 18:11 mdtusz