Overview
I encountered different behaviors of jest.useFakeTimers
when it is called inside beforeEach
versus called outside.
Reproducible Examples
// Foo.tsximport * as React from 'react';import {View} from 'react-native';const Foo: React.FC = () => { const [timePassed, setTimePassed] = React.useState<number>(0); const [flag, setFlag] = React.useState(true); console.log(`timePassed: ${timePassed}, flag: ${flag}`); React.useEffect(() => { if (flag) { setTimeout(() => { if (timePassed > 1) { setFlag(false); } setTimePassed(timePassed + 1); }, 1); } }, [flag, timePassed]); return <View />;};export default Foo;
// foo.outside.test.tsximport * as React from 'react';import {render} from '@testing-library/react-native';import Foo from '../Foo';jest.useFakeTimers();describe('Test Foo', () => { test('1', () => { render(<Foo />); jest.runAllTimers(); }); test('2', () => { render(<Foo />); jest.runAllTimers(); });});
// foo.inside.test.tsximport * as React from 'react';import {render} from '@testing-library/react-native';import Foo from '../Foo';describe('Test Foo', () => { // fake timer usage follows: https://testing-library.com/docs/using-fake-timers/ beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.runOnlyPendingTimers(); jest.useRealTimers(); }); test('1', () => { render(<Foo />); jest.runAllTimers(); }); test('2', () => { render(<Foo />); jest.runAllTimers(); });});
Test Results
outside
$ npx jest __tests__/foo.outside.test.tsx console.log timePassed: 0, flag: true at log (Foo.tsx:8:11) console.log timePassed: 1, flag: true at log (Foo.tsx:8:11) console.log timePassed: 2, flag: true at log (Foo.tsx:8:11) console.log timePassed: 2, flag: false at log (Foo.tsx:8:11) console.log timePassed: 3, flag: false at log (Foo.tsx:8:11) console.log timePassed: 0, flag: true at log (Foo.tsx:8:11) console.log timePassed: 1, flag: true at log (Foo.tsx:8:11) console.log timePassed: 2, flag: true at log (Foo.tsx:8:11) console.log timePassed: 2, flag: false at log (Foo.tsx:8:11) console.log timePassed: 3, flag: false at log (Foo.tsx:8:11) PASS __tests__/foo.outside.test.tsx Test Foo✓ 1 (58 ms)✓ 2 (21 ms)
inside
$ npx jest __tests__/foo.inside.test.tsx 1 ↵ console.log timePassed: 0, flag: true at log (Foo.tsx:8:11) console.log timePassed: 1, flag: true at log (Foo.tsx:8:11) console.log timePassed: 2, flag: true at log (Foo.tsx:8:11) console.log timePassed: 2, flag: false at log (Foo.tsx:8:11) console.log timePassed: 3, flag: false at log (Foo.tsx:8:11) console.log timePassed: 0, flag: true at log (Foo.tsx:8:11) console.log timePassed: 1, flag: true at log (Foo.tsx:8:11) PASS __tests__/foo.inside.test.tsx Test Foo✓ 1 (62 ms)✓ 2 (14 ms)
Observations
It seems that in the outside setup, jest.runAllTimers
runs the timer to the end in both test cases. However, in the inside setup, only the timer in the first test case runs to the end; the timer in the second test case exists prematurely.
Question
Why is the behavior of the fake timer different when jest.useFakeTimers
is called inside versus outside beforeEach
? I expect both setup to run the timer to the end. Thus, the current behavior of the inside setup is a negative surprise.
Machine and Software Info
- macOS Big Sur v11.6.5
- react: 17.0.2
- react-native: 0.65.1
- jest: 28.1.2
- @testing-library/react-native: 10.1.1
- typescript: 4.5.2