From f86b41e7c6d9f9a9ded4953c71e408c2a6529214 Mon Sep 17 00:00:00 2001 From: Ken Gregory Date: Wed, 17 Jun 2020 13:40:46 -0400 Subject: [PATCH] Use realTimers when using fakeTimers A recent change to `runWithRealTimers` added a check for the `clock` attribute on `setTimeout` so that we can determine whether timers were faked with Jest's modern fake timers (which are based on `@sinonjs/fake-timers`). Unfortunately, this attribute can be present when users are not using Jest's fake timers and are using sinonjs. Also, when `useFakeTimers` are invoked without parameters in Jest 26, the legacy timers are used by default. This PR addresses two issues: 1. Don't use realTimers if the timers were not faked by Jest. 2. If we're using realTimers, make sure we're explicit when calling `useFakeTimers` so that the fakes are restored to their original state. --- src/__tests__/helpers.js | 26 +++++++++++++++++++++++++- src/helpers.js | 27 +++++++++++++++++++++------ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/__tests__/helpers.js b/src/__tests__/helpers.js index 327bd407..49eb2b24 100644 --- a/src/__tests__/helpers.js +++ b/src/__tests__/helpers.js @@ -53,18 +53,42 @@ describe('query container validation throws when validation fails', () => { }) }) -test('should always use realTimers before using callback', () => { +test('should always use realTimers before using callback when timers are faked with useFakeTimers', () => { const originalSetTimeout = globalObj.setTimeout + // legacy timers use mocks and do not rely on a clock instance jest.useFakeTimers('legacy') runWithRealTimers(() => { expect(originalSetTimeout).toEqual(globalObj.setTimeout) }) + expect(globalObj.setTimeout._isMockFunction).toBe(true); + expect(globalObj.setTimeout.clock).toBeUndefined(); jest.useRealTimers() + // modern timers use a clock instance instead of a mock jest.useFakeTimers('modern') runWithRealTimers(() => { expect(originalSetTimeout).toEqual(globalObj.setTimeout) }) + expect(globalObj.setTimeout._isMockFunction).toBeUndefined(); + expect(globalObj.setTimeout.clock).toBeDefined(); }) + +test('should not use realTimers when timers are not faked with useFakeTimers', () => { + const originalSetTimeout = globalObj.setTimeout; + + // useFakeTimers is not used, timers are faked in some other way + const fakedSetTimeout = (callback) => { + callback(); + }; + fakedSetTimeout.clock = jest.fn(); + + globalObj.setTimeout = fakedSetTimeout; + + runWithRealTimers(() => { + expect(fakedSetTimeout).toEqual(globalObj.setTimeout) + }) + + globalObj.setTimeout = originalSetTimeout; +}); diff --git a/src/helpers.js b/src/helpers.js index 2a6dd7a9..1a891d0a 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -2,11 +2,26 @@ const globalObj = typeof window === 'undefined' ? global : window // Currently this fn only supports jest timers, but it could support other test runners in the future. function runWithRealTimers(callback) { + const usingJestAndTimers = typeof jest !== 'undefined' && typeof globalObj.setTimeout !== 'undefined'; + const usingLegacyJestFakeTimers = usingJestAndTimers && typeof globalObj.setTimeout._isMockFunction !== 'undefined' && globalObj.setTimeout._isMockFunction; + + let usingModernJestFakeTimers = false; + if ( + usingJestAndTimers && + typeof globalObj.setTimeout.clock !== "undefined" && + typeof jest.getRealSystemTime !== "undefined" + ) { + try { + // jest.getRealSystemTime is only supported for Jest's `modern` fake timers and otherwise throws + jest.getRealSystemTime(); + usingModernJestFakeTimers = true; + } catch { + // not using Jest's modern fake timers + } + } + const usingJestFakeTimers = - globalObj.setTimeout && - (globalObj.setTimeout._isMockFunction || - typeof globalObj.setTimeout.clock !== 'undefined') && - typeof jest !== 'undefined' + usingLegacyJestFakeTimers || usingModernJestFakeTimers; if (usingJestFakeTimers) { jest.useRealTimers() @@ -15,7 +30,7 @@ function runWithRealTimers(callback) { const callbackReturnValue = callback() if (usingJestFakeTimers) { - jest.useFakeTimers() + jest.useFakeTimers(usingModernJestFakeTimers ? 'modern' : 'legacy') } return callbackReturnValue @@ -36,7 +51,7 @@ function getTimeFunctions() { } } -const {clearTimeoutFn, setImmediateFn, setTimeoutFn} = runWithRealTimers( +const { clearTimeoutFn, setImmediateFn, setTimeoutFn } = runWithRealTimers( getTimeFunctions, )