javascript / intermediate
Snippet
Testing React Async State Fallbacks with Testing Library
Testing asynchronous state mutations in React components requires mocking network boundaries and asserting intermediate loading states alongside final error boundaries. Using `waitFor` along with async `userEvent` ensures that microtasks and state updates resolve predictably without race conditions in tests.
snippet.js
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { render, screen, waitFor } from '@testing-library/react';import userEvent from '@testing-library/user-event';import { describe, it, expect, vi } from 'vitest';import { UserProfile } from './UserProfile';describe('UserProfile async error boundaries', () => {it('renders an error message when the API rejects', async () => {const user = userEvent.setup();const mockFetch = vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Service Unavailable'));render(<UserProfile userId="user_123" />);const refreshButton = screen.getByRole('button', { name: /refresh/i });await user.click(refreshButton);expect(screen.getByText(/loading/i)).toBeInTheDocument();await waitFor(() => {expect(screen.getByRole('alert')).toHaveTextContent('Service Unavailable');});mockFetch.mockRestore();});});
react
Breakdown
1
vi.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Service Unavailable'));
Stubs the global fetch API to simulate an asynchronous network rejection for one execution.
2
await user.click(refreshButton);
Dispatches user interaction events asynchronously, simulating real browser event bubbling.
3
await waitFor(() => { expect(screen.getByRole('alert')).toHaveTextContent(...); });
Polls the DOM assertion repeatedly until the asynchronous error state triggers component re-rendering.