Why is the behavior of `jest.useFakeTimers` different when called inside vs. outside of `beforeEach` in React Native

658 views Asked by At

Overview

I encountered different behaviors of jest.useFakeTimers when it is called inside beforeEach versus called outside.

Reproducible Examples

// Foo.tsx

import * 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.tsx
import * 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.tsx
import * 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
0

There are 0 answers