nock: Timeout when using jest "useFakeTimers" functionality

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

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 27
  • Comments: 25 (2 by maintainers)

Most upvoted comments

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'))

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…

https://github.com/testing-library/user-event/issues/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 });

None of these worked for me

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"],
});

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?

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!

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

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

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 😓

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

https://github.com/nock/nock/blob/3efd7382011a8d1ca084bfc4043a332a796a0618/lib/intercepted_request_router.js#L66-L69

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.

@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

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

Instead of

jest.useFakeTimers('legacy')

it should be

jest.useFakeTimers({
    legacyFakeTimers: true
})

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!

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);