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

Add automatic time advance for fake timers #10602

Closed
daniel-mina97 opened this issue Oct 7, 2020 · 9 comments 路 Fixed by #12572
Closed

Add automatic time advance for fake timers #10602

daniel-mina97 opened this issue Oct 7, 2020 · 9 comments 路 Fixed by #12572

Comments

@daniel-mina97
Copy link

daniel-mina97 commented Oct 7, 2020

馃殌 Feature Proposal

When using the fake timers, there should be an option to advance time automatically. An example of this can be seen In @sinonjs/fake-timers where you can set the system time to a particular time stamp and also have the clock advance naturally:

const clock = FakeTimers.install({ now: 1601488969000, shouldAdvanceTime: true });

Motivation

For my particular use case, I have a saved json object that was the result of running some ETL (extract, transform, load) code on a certain date. Within this json object is a field "DaysSinceCreated" that has a static value, let's say for this example 30. For my test, I want in the end to match this saved json object with the output of the ETL code. The ETL code makes use of a bottleneck library to schedule the API calls to extract the initial data, therefore relying on timers to work correctly. I want to be able to change the "current" date, as to always produce a "DaysSinceCreated" value of 30 no matter when the test is run, but also have the timers progress without having to manually trigger them.

Example

Example described above ^^

Pitch

This feature belongs in jest as it will allow users to write their tests to run on a different "current time" AND have timers progress naturally without having to directly include @sinonjs/fake-timers.

@jugglinmike
Copy link

Potential workaround:

+// Automatically advance simulated time
+setInterval(() => {
+  jest.advanceTimersByTime(20);
+}, 20);
 jest.useFakeTimers('modern');

This is based on Sinon's description of the functionality:

Please note that this is achieved using the original setImmediate() API at a certain configurable interval config.advanceTimeDelta (default: 20ms). Meaning time would be incremented every 20ms, not in real time.

Automatic advancement of simulated time seems like a general requirement whenever precise timeout intervals are an implementation detail. In such cases, developers won't want to hard-code progression instructions into their tests.

@ivan7237d
Copy link

I wanted to bring up two more use-cases where automatically advanced timers would be super-handy.

The first use-case is for-await syntax. Imagine you need to test an async generator, e.g.

/**
 * Returns a promise resolving to 42 after a user-provided timeout.
 */
const timer = (duration: number) =>
  new Promise((resolve) => {
    setTimeout(() => resolve(42), duration);
  });

/**
 * Async generator that we need to test.
 */
async function* asyncIterable() {
  const result = await timer(500);
  yield result;
}

If you write a test like

for await (const value of asyncIterable()) {
  // advance timers and make assertions
}

this is not going to work: you need to advance timers after the promise returned by the iterator was created, but before awaiting on it, and for-await syntax doesn't let you do that.

The second use-case is a library I've written that lets you create Jest snapshots of log messages. These messages include time deltas, so when you write tests you don't have to advance timers by a specific amount of time. Here's an example:

import { getMessages, log } from '1log';

/**
 * The function that we'll be testing, same as the one used in previous example.
 */
const timer = (duration: number) =>
  new Promise((resolve) => {
    setTimeout(() => resolve(42), duration);
  });

test('timer', async () => {
  // Wrapping an expression in `log` has no effect other than creating some log messages.
  const promise = log(timer(500));
  jest.runAllTimers();
  await promise;
  // `getMessages` retrieves log messages.
  expect(getMessages()).toMatchInlineSnapshot(`
    [create 1] +0ms Promise {}
    [create 1] [resolve] +500ms 42
  `);
});

With auto-advanced timers, the first three lines of code in the test function would become just

await log(timer(500));

@ivan7237d
Copy link

ivan7237d commented Mar 5, 2021

The workaround I'm currently using is enclosing async tests in autoAdvanceTimers, e.g. test('...', autoAdvanceTimers(async () => {...})), with autoAdvanceTimers defined as follows:

import { setImmediate } from 'timers';

const autoAdvanceTimers = <Result>(
  callback: () => Promise<Result>,
) => async () => {
  const promise = callback();
  let resolved = false;
  promise.then(() => {
    resolved = true;
  });
  while (!resolved) {
    await new Promise(setImmediate);
    if (jest.getTimerCount() === 0) {
      break;
    }
    jest.advanceTimersToNextTimer();
  }
  return await promise;
};

@sibelius
Copy link

sibelius commented May 3, 2021

this is causing this error for me:

ReferenceError: You are trying to access a property or method of the Jest environment after it has been torn down.

@SimenB
Copy link
Member

SimenB commented Dec 22, 2021

I'd like to add some way of turning shouldAdvanceTime on when using modern timers. Not sure what API makes the most sense, tho. Ideas?

@qrohlf
Copy link

qrohlf commented Jan 4, 2022

On the surface, I sort of expected that the jest.useFakeTimers() call would take a config object matching the config available in the sinonjs/fake-timers README, since that was the documentation linked in the feature announcement for modern timers.

Any reason why it would be a bad idea to just pass-thru the config like that? (i.e. jest.useFakeTimer({shouldAdvanceTime: true})?

@SimenB
Copy link
Member

SimenB commented Jan 5, 2022

Any reason why it would be a bad idea to just pass-thru the config like that? (i.e. jest.useFakeTimer({shouldAdvanceTime: true})?

Nope, PR welcome! 馃檪

@SimenB
Copy link
Member

SimenB commented Apr 5, 2022

@github-actions
Copy link

github-actions bot commented May 6, 2022

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 May 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
6 participants