Skip to content

Commit 00e146a

Browse files
committed
fix fake timers explanation and usage in code
1 parent fe84ab4 commit 00e146a

File tree

1 file changed

+40
-25
lines changed

1 file changed

+40
-25
lines changed

versioned_docs/version-7.x/testing.md

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export const MyTabs = () => {
166166
```js
167167
import { expect, jest, test } from '@jest/globals';
168168
import { createStaticNavigation } from '@react-navigation/native';
169-
import { render, screen, userEvent } from '@testing-library/react-native';
169+
import { act, render, screen, userEvent } from '@testing-library/react-native';
170170

171171
import { MyTabs } from './MyTabs';
172172

@@ -181,6 +181,8 @@ test('navigates to settings by tab bar button press', async () => {
181181
const button = screen.getByRole('button', { name: 'Settings, tab, 2 of 2' });
182182
await user.press(button);
183183

184+
act(() => jest.runAllTimers());
185+
184186
expect(screen.getByText('Settings screen')).toBeVisible();
185187
});
186188
```
@@ -191,7 +193,7 @@ test('navigates to settings by tab bar button press', async () => {
191193
```js
192194
import { expect, jest, test } from '@jest/globals';
193195
import { NavigationContainer } from '@react-navigation/native';
194-
import { render, screen, userEvent } from '@testing-library/react-native';
196+
import { act, render, screen, userEvent } from '@testing-library/react-native';
195197

196198
import { MyTabs } from './MyTabs';
197199

@@ -209,28 +211,48 @@ test('navigates to settings by tab bar button press', async () => {
209211
const button = screen.getByRole('button', { name: 'Settings, tab, 2 of 2' });
210212
await user.press(button);
211213

214+
act(() => jest.runAllTimers());
215+
212216
expect(screen.getByText('Settings screen')).toBeVisible();
213217
});
214218
```
215219

216220
</TabItem>
217221
</Tabs>
218222

219-
First, we need to create a User Event object instance from `react-native-testing-library` in order to be able to trigger user events.
223+
When writing tests containing navigation with animations, you need to wait until animations finish before proceeding further. To do so, you have to use **fake timers**. [`Fake Timers`](https://jestjs.io/docs/timer-mocks) replace real implementation of the native timer functions (e.g. `setTimeout()`, `setInterval()`) with a custom implementation that uses a fake clock. They allow you to instantly skip animation time using `jest.runAllTimers()`. To avoid getting state change error, wrap `runAllTimers` in `act`.
224+
225+
```js
226+
// Enable fake timers
227+
jest.useFakeTimers();
228+
229+
// ...
230+
231+
// Wrap jest.runAllTimers in act to prevent state change error
232+
// Skip all timers including animations
233+
act(() => jest.runAllTimers());
234+
```
235+
236+
Even though `BottomTabNavigator` by default does not use any animations, some components of the tab bar do so. If you don't use fake timers in this example, the tests might still pass but you will get a warning from `react-native-tesing-library` about React state updates.
237+
238+
After we setup fake timers, we need to create a User Event object instance from `react-native-testing-library` in order to be able to trigger user events.
220239

221240
```js
222241
// Create User Event object instance
223242
const user = userEvent.setup();
224243
```
225244

226-
After we create and render our tabs, we get the settings tab bar button using an accessibility label assigned to it and press it using `user.press(button)`.
245+
After we create and render our tabs, we get the settings tab bar button using an accessibility label assigned to it and press it using `user.press(button)`. We use fake timers to skip the animations.
227246

228247
```js
229248
// Get the setting tab bar button
230249
const button = screen.getByRole('button', { name: 'Settings, tab, 2 of 2' });
231250

232251
// Simulate user pressing the button
233252
await user.press(button);
253+
254+
// Skip tab bar animations
255+
act(() => jest.runAllTimers());
234256
```
235257

236258
We expect that after pressing the button, the screen will change and `'Settings screen'` will be visible.
@@ -399,23 +421,6 @@ test('surprise text appears after transition to surprise screen is complete', as
399421

400422
We press the "Click here!" button using `user.press()` and check that the text does not appear right away but only after the transition between screens ends.
401423

402-
<!-- When writing tests containing navigation with animations (in this example we have a `StackNavigator`, which uses an animation for the transition based on the platform and OS version) you need to wait until animations finish before proceeding further. To do so, you have to use `fake timers`. [`Fake Timers`](https://jestjs.io/docs/timer-mocks) replace real implementation of times function to use fake clock. They allow you to instantly skip animation time. To avoid getting state change error, wrap `runAllTimers` in `act`.
403-
404-
```js
405-
// Enable fake timers
406-
jest.useFakeTimers();
407-
408-
// ...
409-
410-
// Wrap jest.runAllTimers in act to prevent state change error
411-
// Skip all timers including animations
412-
act(() => jest.runAllTimers());
413-
```
414-
415-
If we hadn't used fake timers in this example, the test would have failed.
416-
417-
In the previous example we didn't use fake timers because `BottomTabNavigator` by default does not use any transition animations. -->
418-
419424
### Example 3 - Enforce navigator state in response to navigation event
420425

421426
Display settings screen after settings tab bar button is pressed.
@@ -567,7 +572,7 @@ export function MyTabs() {
567572
```js
568573
import { expect, jest, test } from '@jest/globals';
569574
import { createStaticNavigation } from '@react-navigation/native';
570-
import { render, screen, userEvent } from '@testing-library/react-native';
575+
import { act, render, screen, userEvent } from '@testing-library/react-native';
571576

572577
import { MyTabs } from './MyTabs';
573578

@@ -588,15 +593,19 @@ test('displays settings screen after settings tab bar button press', async () =>
588593
});
589594

590595
await user.press(settingsTabButton);
596+
act(() => jest.runAllTimers());
591597
expect(screen.getByText('Settings screen')).toBeVisible();
592598

593599
await user.press(screen.getByText('Go to Details'));
600+
act(() => jest.runAllTimers());
594601
expect(screen.getByText('Details screen')).toBeVisible();
595602

596603
await user.press(homeTabButton);
604+
act(() => jest.runAllTimers());
597605
expect(screen.getByText('Home screen')).toBeVisible();
598606

599607
await user.press(settingsTabButton);
608+
act(() => jest.runAllTimers());
600609
expect(screen.getByText('Settings screen')).toBeVisible();
601610
});
602611
```
@@ -607,7 +616,7 @@ test('displays settings screen after settings tab bar button press', async () =>
607616
```js
608617
import { expect, jest, test } from '@jest/globals';
609618
import { NavigationContainer } from '@react-navigation/native';
610-
import { render, screen, userEvent } from '@testing-library/react-native';
619+
import { act, render, screen, userEvent } from '@testing-library/react-native';
611620

612621
import { MyTabs } from './MyTabs';
613622

@@ -631,15 +640,19 @@ test('displays settings screen after settings tab bar button press', async () =>
631640
});
632641

633642
await user.press(settingsTabButton);
643+
act(() => jest.runAllTimers());
634644
expect(screen.getByText('Settings screen')).toBeVisible();
635645

636646
await user.press(screen.getByText('Go to Details'));
647+
act(() => jest.runAllTimers());
637648
expect(screen.getByText('Details screen')).toBeVisible();
638649

639650
await user.press(homeTabButton);
651+
act(() => jest.runAllTimers());
640652
expect(screen.getByText('Home screen')).toBeVisible();
641653

642654
await user.press(settingsTabButton);
655+
act(() => jest.runAllTimers());
643656
expect(screen.getByText('Settings screen')).toBeVisible();
644657
});
645658
```
@@ -649,8 +662,6 @@ test('displays settings screen after settings tab bar button press', async () =>
649662

650663
We get tab bar buttons, press them and check if rendered screens are correct.
651664

652-
<!-- In this example, we don't need to use fake timers because text from the next screen is available using `getByText` even before the animation ends. -->
653-
654665
### Example 4 - `useFocusEffect` hook and data fetching
655666

656667
On profile screen focus, display loading state while waiting for data and then show fetched profile on every refocus.
@@ -884,6 +895,8 @@ test('on profile screen focus, displays loading state while waiting for data and
884895
expect(screen.getByText('ditto')).toBeVisible();
885896

886897
await user.press(homeTabButton);
898+
await act(() => jest.runAllTimers());
899+
887900
await user.press(profileTabButton);
888901
expect(screen.queryByText('Loading...')).not.toBeVisible();
889902

@@ -930,6 +943,8 @@ test('on profile screen focus, displays loading state while waiting for data and
930943
expect(screen.getByText('ditto')).toBeVisible();
931944

932945
await user.press(homeTabButton);
946+
await act(() => jest.runAllTimers());
947+
933948
await user.press(profileTabButton);
934949
expect(screen.queryByText('Loading...')).not.toBeVisible();
935950

0 commit comments

Comments
 (0)