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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
9still opened this issue Sep 24, 2020 · 9 comments
Open

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

9still opened this issue Sep 24, 2020 · 9 comments

Comments

@9still
Copy link

9still commented Sep 24, 2020

馃殌 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.

@cschwebk
Copy link

cschwebk commented Oct 20, 2021

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

@kentcdodds
Copy link
Contributor

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

@9still
Copy link
Author

9still commented Oct 20, 2021

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.

@CreativeTechGuy
Copy link

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! 馃槈

@mdtusz
Copy link
Contributor

mdtusz commented May 24, 2022

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.

@vkarpov15
Copy link

vkarpov15 commented Oct 5, 2022

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() ?

@github-actions
Copy link

github-actions bot commented Oct 5, 2023

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 github-actions bot added the Stale label Oct 5, 2023
@CreativeTechGuy
Copy link

Not stale

@github-actions github-actions bot removed the Stale label Oct 5, 2023
@ogonkov
Copy link

ogonkov commented Nov 21, 2023

jest@29.7.0 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;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants