Integration Tests — A Whole Feature
AdvancedRendering a real route with real providers and walking through a flow catches the wiring bugs that unit tests structurally cannot, at a fraction of the cost of an E2E suite.
Overview
The most valuable React tests are usually neither unit nor end-to-end. Rendering a route with the router, the query client and the auth provider in place, then completing a flow the way a user would, exercises the integration points where things actually break: a form that validates but never submits, a mutation that succeeds without refreshing the list, a guard that redirects the wrong way. These run in milliseconds, need no browser, and give most of the confidence of an E2E test — which is why the middle of the testing pyramid is where the effort belongs.
A Whole Flow
One test covering form, validation, mutation, invalidation and navigation.
it('creates a problem and shows it in the list', async () => {
const { user } = renderWithProviders(<App />, { route: '/admin/problems/new' })
await user.type(screen.getByLabelText(/title/i), 'Two Sum')
await user.selectOptions(screen.getByLabelText(/difficulty/i), 'easy')
await user.click(screen.getByRole('button', { name: /publish/i }))
// navigated
expect(await screen.findByRole('heading', { name: 'Two Sum' })).toBeInTheDocument()
// and the list was invalidated, not left stale
await user.click(screen.getByRole('link', { name: /all problems/i }))
expect(await screen.findByText('Two Sum')).toBeInTheDocument()
})
// This single test would have caught: a broken submit handler, a
// validation rule blocking valid input, a missing invalidation, and
// a wrong redirect. Four unit tests would have caught none of them,
// because each of those bugs lives BETWEEN the units.Testing Routing and Guards
MemoryRouter gives you a real router with a controllable starting point.
it('redirects a signed-out user to sign in', async () => {
server.use(http.get('/api/me', () => new HttpResponse(null, { status: 401 })))
renderWithProviders(<App />, { route: '/dashboard' })
expect(await screen.findByRole('heading', { name: /sign in/i })).toBeInTheDocument()
})
it('returns the user to where they were going after signing in', async () => {
const { user } = renderWithProviders(<App />, { route: '/dashboard' })
await signIn(user)
expect(await screen.findByRole('heading', { name: /dashboard/i })).toBeInTheDocument()
})
it('renders a 404 for an unknown route', () => {
renderWithProviders(<App />, { route: '/nope' })
expect(screen.getByText(/page not found/i)).toBeInTheDocument()
})
// Guards, redirects and 404s are pure wiring, invisible to unit
// tests, and among the most common things to break in a refactor.Keeping the Suite Trustworthy
Where integration tests sit, and what makes them rot.
// /\ E2E (Playwright) — a handful of critical journeys
// / \ INTEGRATION — feature flows, most of the value
// /____\ Unit — pure logic and branchy functions
// What makes an integration suite rot:
// - shared mutable fixtures, so tests depend on order
// - assertions on incidental text that copy changes break
// - one enormous test asserting fifteen things
// Instead:
// - build fixtures per test with a factory
const makeProblem = (over = {}) => ({ id: 1, slug: 'two-sum', ...over })
// - assert by role and accessible name, which survives copy edits
// - one behaviour per test, named as a sentence about the user
// Run them in CI on every PR. An integration suite nobody runs is
// a suite that will be deleted the first time it goes red.
// And keep them fast: no arbitrary waits, no real network, no
// unnecessary providers.Key Points to Remember
- 1Integration tests render a real route with real providers and catch bugs that live between units
- 2One flow test can cover validation, submission, cache invalidation and navigation at once
- 3MemoryRouter with an initial route makes guards, redirects and 404s testable without a browser
- 4Build fixtures per test with factories and query by role, so tests do not depend on order or exact copy
- 5Integration tests are the widest useful layer — faster than E2E and far more revealing than unit tests alone
Interview Questions
Sign in to ask AriaWhat kind of bug does an integration test catch that unit tests cannot?
How would you test that signing in returns the user to the page they originally requested?
What makes an integration test suite become unreliable over time?
Ask Aria about Integration Tests — A Whole Feature
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.