DEV Community

Diego Carreira de França
Diego Carreira de França

Posted on • Edited on

🧪 React Testing: Tips That Actually Work

1. Test What Users See, Not What Developers Write

// 👎 Don't do this test('component sets loading state', () => { const wrapper = shallow(<UserProfile />); expect(wrapper.state('isLoading')).toBe(true); }); // 👍 Do this instead test('shows loading state to users', () => { render(<UserProfile />); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); 
Enter fullscreen mode Exit fullscreen mode

2. Smart Ways to Test User Actions

// 👎 Testing implementation details test('updates email state', () => { const wrapper = shallow(<SignupForm />); wrapper.find('input').simulate('change'); expect(wrapper.state('email')).toBe('test@email.com'); }); // 👍 Testing user behavior test('lets user submit their email', () => { const handleSubmit = jest.fn(); render(<SignupForm onSubmit={handleSubmit} />); // Type like a real user would userEvent.type( screen.getByRole('textbox', { name: /email/i }), 'test@email.com' ); // Click like a real user would userEvent.click(screen.getByRole('button', { name: /submit/i })); expect(handleSubmit).toHaveBeenCalledWith('test@email.com'); }); 
Enter fullscreen mode Exit fullscreen mode

3. Stop Mocking Everything

// 👎 Over-mocking jest.mock('./UserAvatar'); jest.mock('./UserBio'); jest.mock('./UserStats'); // 👍 Only mock what's necessary (like APIs) const server = setupMSW([ rest.get('/api/user', (req, res, ctx) => { return res(ctx.json({ name: 'John' })) }) ]); 
Enter fullscreen mode Exit fullscreen mode

4. Testing Hooks? Keep It Simple

// 👎 Complex setup const wrapper = mount( <Provider store={store}> <ThemeProvider theme={theme}> <HookComponent /> </ThemeProvider> </Provider> ); // 👍 Just test the logic const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); 
Enter fullscreen mode Exit fullscreen mode

5. Write Fewer, Better Tests (Meaningful tests)

// 👎 Testing every little thing test('renders header'); test('renders subheader'); test('renders each list item'); test('renders footer'); // 👍 Test important flows test('lets user complete checkout flow', async () => { render(<CheckoutFlow />); await userEvent.type(screen.getByLabelText(/card/i), '4242...'); await userEvent.click(screen.getByText(/pay/i)); expect(screen.getByText(/thanks for your order/i)).toBeInTheDocument(); }); 
Enter fullscreen mode Exit fullscreen mode

6. Smart Selectors

// 👎 easy-to-break selectors getByTestId('submit-button') querySelector('.submit-btn') // 👍 Resilient selectors getByRole('button', { name: /submit/i }) getByLabelText(/email/i) getByText(/welcome/i) 
Enter fullscreen mode Exit fullscreen mode

7. Testing Loading States

// 👍 Handle common async patterns test('shows loading and then content', async () => { render(<UserProfile />); // Check loading state expect(screen.getByText(/loading/i)).toBeInTheDocument(); // Wait for data await screen.findByText('John Doe'); // Loading should be gone expect(screen.queryByText(/loading/i)).not.toBeInTheDocument(); }); 
Enter fullscreen mode Exit fullscreen mode

8. Test Error States

// 👍 Don't forget error handling test('handles API errors gracefully', async () => { server.use( rest.get('/api/user', (req, res, ctx) => { return res(ctx.status(500)) }) ); render(<UserProfile />); await screen.findByText(/something went wrong/i); expect(screen.getByRole('button', { name: /try again/i })).toBeInTheDocument(); }); 
Enter fullscreen mode Exit fullscreen mode

🎯 Quick Checklist Before Pushing

  • ✅ Tests pass
  • ✅ Tests are meaningful
  • ✅ Used realistic user interactions
  • ✅ Focused on behavior intead of implementation
  • ✅ Covered error states
  • ✅ Used resilient selectors
  • ✅ Mocked only what's necessary

💡 Remember

  • Tests should give you confidence to refactor
  • If tests break when you refactor (but the app works), your tests are wrong
  • Test behavior, not implementation
  • When in doubt, test like a user

Top comments (0)