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

Timeout when using jest "useFakeTimers" functionality #2200

Open
sdomagala opened this issue May 27, 2021 · 25 comments
Open

Timeout when using jest "useFakeTimers" functionality #2200

sdomagala opened this issue May 27, 2021 · 25 comments
Labels

Comments

@sdomagala
Copy link

What is the expected behavior?

When mocking timers in jest I would expect mocks to pass properly as those (to me) shouldn't be related to network communication.

jest
  .useFakeTimers('modern')
  .setSystemTime(new Date('2020-01-01').getTime())

What is the actual behavior?

Test case hangs until the whole suite times out

Possible solution
Make mocks not reliant on the passing time, or maybe provide a flag that would handle this behaviour differently?

How to reproduce the issue

import nock from 'nock'
import axios from 'axios'

jest
  .useFakeTimers('modern')
  .setSystemTime(new Date('2020-01-01').getTime())

describe('Test', () => {
  const url = 'http://www.google.com'

  
  beforeEach(() => nock(url).get('/').reply(201))
  afterEach(() => expect(nock.isDone()).toEqual(true))
  
  it('should pass this test', async () => {
    const res = await axios(url)
    expect(res.status).toEqual(201)
  })
})

Remove fake timers and the test case passes

Does the bug have a test case?

Yes, provided above

Versions

Software Version(s)
Nock v13.0.11
Node v14.16.0
TypeScript v3.9.7
@imcotton
Copy link

This happens to me right after upgrading Jest from v26 to v27, @sdomagala are you in the same condition as well?

@gr2m
Copy link
Member

gr2m commented May 28, 2021

I've read that jest changed something with its timers API with the 27 release. My guess is that it interferes with async processes / timers that nock is using internally.

@imcotton
Copy link

By specifying jest.useFakeTimers('legacy') (instead of default modern) would mitigate the hanging for me.

Quote from Jest blog post on v27 release [1]:

Another default that we are changing affects Fake Timers aka Timer Mocks. We introduced an opt-in "modern" implementation of Fake Timers in Jest 26 accessed transparently through the same API, but with much more comprehensive mocking, such as for Date and queueMicrotask.
This modern fake timers implementation will now be the default. If you are among the unlucky few who are affected by the subtle implementation differences too heavily to migrate, you can get back the old implementation using jest.useFakeTimers("legacy") or, if you are enabling fake timers globally via configuration, "timers": "legacy".


1: https://jestjs.io/blog/2021/05/25/jest-27

@sdomagala
Copy link
Author

@imcotton for me it happened the moment I've tried to use those modern timers and function setSystemTime. I believe it was first introduced in jest 26.

Using legacy timer indeed resolves the issue in this test case but it doesn't let you set global time so it doesn't really help me.

Per docs:

Note: This function is only available when using modern fake timers implementation

@PaperStrike
Copy link

PaperStrike commented May 28, 2021

Recently I experienced this too. It seems like nock uses "greater than" to test if the current time has passed the defined delay time (0 if not set explicitly), and respond only when the test assessed true. As Jest's modern version timer locks, nock will not respond.

Try to advance at somewhere:

jest.useFakeTimers();
const url = 'https://example';
nock(url).get('/').reply(201);

const axiosPromise = axios(url);

// Advance 1ms for nock delay.
jest.advanceTimersByTime(1);

// Got the response now.
const res = await axiosPromise;
expect(res.status).toBe(201);

@gr2m
Copy link
Member

gr2m commented May 28, 2021

if you could digg into that and see if we could change the implementation to something more resilient that will work with jest's new fake timers that'd be great

@PaperStrike
Copy link

PaperStrike commented May 29, 2021

// Emit a fake socket event on the next tick to mimic what would happen on a real request.
// Some clients listen for a 'socket' event to be emitted before calling end(),
// which causes Nock to hang.
process.nextTick(() => this.connectSocket())

Here, Jest's modern fake timer completely locks the time including the tick, so the connect is never reached, and a Jest.runAllTicks is enough.

@PaperStrike
Copy link

jestjs/jest#10221

@tjhiggins
Copy link

Also broken with sinon.useFakeTimers. The first mocked calls works, but any subsequent call times out.

@hotyhuang
Copy link

Add an eye here, bcs I just faced a same issue, with pretty much the same codes as above. But in my case, using jest.useFakeTimers('legacy') does not solve the problem.

Versions:

"jest": "26.6.3",
"nock": "12.0.3",
"typescript": "4.4.2",
"node": "14.18.1",

@bpinto
Copy link

bpinto commented May 27, 2022

I've tried the recently added doNotFake option (jest.useFakeTimers({ doNotFake: ['nextTick'] })) but it didn't work for me. Has anyone had any success with that new option?

@Inviter
Copy link

Inviter commented Jul 29, 2022

Using setImmediate (jest.useFakeTimers({ doNotFake: ['setImmediate'] })) worked for me (I'm using request from supertest)

@josh-meredith
Copy link

josh-meredith commented Sep 8, 2022

If you're still having issues, try adding both nextTick and setImmediate to the array, this is the only way it'd work for me.

jest.useFakeTimers({ doNotFake: ['nextTick', 'setImmediate'] }).setSystemTime(new Date('2022-01-01'))

@chornos13
Copy link

yeah, that's true, before I found this answer, I tried one by one to not fake this functionality

            'Date',
            'hrtime',
            'performance',
            'queueMicrotask',
            'requestAnimationFrame',
            'cancelAnimationFrame',
            'requestIdleCallback',
            'cancelIdleCallback',
            'clearImmediate',
            'setInterval',
            'clearInterval',
            'setTimeout',
            'clearTimeout',
            'nextTick',
            'setImmediate',

and found out to making fakeTimers working is to not faking "'nextTick', 'setImmediate'" but I still don't know what effect If I do that on my test 😓

@SergioRBJ
Copy link

If you're still having issues, try adding both nextTick and setImmediate to the array, this is the only way it'd work for me.

jest.useFakeTimers({ doNotFake: ['nextTick', 'setImmediate'] }).setSystemTime(new Date('2022-01-01'))

This worked for me, thanks!

@NTag
Copy link

NTag commented Oct 17, 2022

I followed @chornos13 comment, started with the whole list into doNotFake, and removed one by one elements. The setup which is working on my computer (my setup is jest and supertest) is:

import { jest } from "@jest/globals";

jest
  .useFakeTimers({
    doNotFake: [
      "nextTick",
      "setImmediate",
      "clearImmediate",
      "setInterval",
      "clearInterval",
      "setTimeout",
      "clearTimeout",
    ],
  })
  .setSystemTime(new Date("2022-02-15"));

I don't understand why Jest doesn't offer a way to just change the date without mocking the universe…

@swimmie0
Copy link

Changing beforeEach statement to beforeAll statement worked for me. (jest version 27.x)

@afroguy16
Copy link

None of these worked for me

@globalmatt
Copy link

globalmatt commented Mar 7, 2023

Using setImmediate (jest.useFakeTimers({ doNotFake: ['setImmediate'] })) worked for me (I'm using request from supertest)

Fantastic, this fixed it for me! I'm using supertest also.

When an error was thrown inside an async route handler in my Express app, and I used jest.useFakeTimers(), the error was not being reported and the test timed out. Your solution fixed it, and now errors are successfully thrown and reported without the test timing out.

doNotFake: ['nextTick'] had no effect, but doNotFake: ['setImmediate'] did the trick. Thanks so much!

@mohamedjkr
Copy link

I wrote a small function to fix this issue by extending jest.useFakeTimers options with a new fake option.

/**
 * Adds `fake` option to `jest.useFakeTimers` config api
 *
 * @param config Fake timers config options
 *
 * @return Jest instance
 */
function useFakeTimers(config?: FakeTimersConfig & { fake?: FakeableAPI[] }) {
  if (config?.fake) {
    if (config.doNotFake) {
      throw new Error('Passing both `fake` and `doNotFake` options to `useFakeTimers()` is not supported.')
    }
    
    const { fake, ...options } = config
    return jest.useFakeTimers({
      ...options,
      doNotFake: Array<FakeableAPI>(
        'Date',
        'hrtime',
        'nextTick',
        'performance',
        'queueMicrotask',
        'requestAnimationFrame',
        'cancelAnimationFrame',
        'requestIdleCallback',
        'cancelIdleCallback',
        'setImmediate',
        'clearImmediate',
        'setInterval',
        'clearInterval',
        'setTimeout',
        'clearTimeout',
      ).filter((api) => !fake.includes(api)),
    })
  }

  return jest.useFakeTimers(config)
}

Usage

  beforeAll(async () => {
    useFakeTimers({ fake: ['Date'] })
  })

cc @NTag

@WtfJoke
Copy link

WtfJoke commented Mar 28, 2023

The same issue also appears in vitest (as they use the same timer, but the doNotFake property doesnt exist there).
Solved it there by using the following config:

vi.useFakeTimers({
    shouldAdvanceTime: true,
    toFake: ["Date"],
});

@sezanzeb
Copy link

Instead of

jest.useFakeTimers('legacy')

it should be

jest.useFakeTimers({
    legacyFakeTimers: true
})

@phegman
Copy link

phegman commented Aug 30, 2023

testing-library/user-event#833 might be your issue if you are using testing library user events. Took me forever to figure out but this eventually fixed it for me:

jest.useFakeTimers({ advanceTimers: true });

@abdelhalim97
Copy link

i had to add { advanceTimers: true } to useFakeTimers like this:

jest.useFakeTimers({ advanceTimers: true }).setSystemTime(dateAfterHour);

also i had to delete this from jest config:

fakeTimers: {
enableGlobally: true,
},

adding advanceTimers;true to global config did not help
note: i m using jest 29 without nock

@roc
Copy link

roc commented Apr 24, 2024

In the context of fastify inject I found that it was essential to use the advanceTimers: true config to avoid timeouts.

beforeAll(async () => {
    jest
      .useFakeTimers({ advanceTimers: true })
      .setSystemTime(new Date('2022-04-24'));
  });

  afterAll(async () => {
    jest.useRealTimers();
  });

I'm not a fastify person, but I'm guessing this is because the test inject relies on time advancing in order to return its response.

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

No branches or pull requests