Testinghighintegration
Write integration tests for key workflows
rule · integration-testing
Integration tests verify that separate units of code work correctly when connected together, catching the bugs that unit tests can't.
Code Example
JavaScript
// test/api/users.integration.test.js
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import supertest from 'supertest'
import { app } from '../src/app.js'
import { db } from '../src/db.js'
describe('POST /api/users', () => {
beforeAll(async () => {
await db.migrate.latest()
await db.seed.run()
})
afterAll(async () => {
await db.destroy()
})
it('creates a user with valid data', async () => {
const response = await supertest(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@example.com' })
.expect(201)
expect(response.body).toMatchObject({
id: expect.any(Number),
name: 'Alice',
email: 'alice@example.com'
})
// Verify it was actually saved
const saved = await db('users').where({ email: 'alice@example.com' }).first()
expect(saved).toBeDefined()
})
it('returns 400 for duplicate email', async () => {
await supertest(app)
.post('/api/users')
.send({ name: 'Bob', email: 'existing@example.com' })
.expect(400)
})
})Why It Matters
Unit tests verify individual functions in isolation, but they can't catch the bugs that occur when those functions interact — a perfectly correct validation function and a correct database function can still fail when wired together incorrectly. Integration tests catch these wiring bugs before they reach production, where they're expensive to diagnose.
Component Integration with React Testing Library
JSX
// test/UserProfileForm.integration.test.jsx
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { QueryClientProvider } from '@tanstack/react-query'
import { UserProfileForm } from '../src/components/UserProfileForm'
import { server } from './mocks/server'
import { rest } from 'msw'
describe('UserProfileForm', () => {
it('submits updated profile and shows success message', async () => {
const user = userEvent.setup()
render(
<QueryClientProvider client={queryClient}>
<UserProfileForm userId={1} />
</QueryClientProvider>
)
// Fill in the form
const nameInput = screen.getByLabelText('Full name')
await user.clear(nameInput)
await user.type(nameInput, 'Alice Updated')
// Submit
await user.click(screen.getByRole('button', { name: 'Save changes' }))
// Verify success feedback (tests form + API call + state update + UI render)
await waitFor(() => {
expect(screen.getByText('Profile saved successfully')).toBeInTheDocument()
})
})
it('shows field errors when API returns validation failure', async () => {
server.use(
rest.put('/api/users/:id', (req, res, ctx) =>
res(ctx.status(422), ctx.json({ errors: { name: 'Name is too long' } }))
)
)
// ... test error display
})
})What to Integration Test
Text
High-value integration test targets:
✓ API routes + database queries (the full server-side stack)
✓ Form components + submission + API response + UI feedback
✓ Authentication flows (login → session → protected route)
✓ Shopping cart + checkout + order creation
✓ File upload + processing + storage + thumbnail display
✓ Search input + query → results display
Skip (better as unit tests):
✗ Pure utility functions
✗ Simple transformations
✗ Components with no external dependenciesStandards
- Use these references as the standard for how the test or monitoring strategy should behave in the shipped workflow.
- Check the implementation against Playwright Docs before treating the rule as satisfied.
- Check the implementation against Testing Library Guiding Principles before treating the rule as satisfied.
Verification
- Run the relevant test or CI step locally and confirm it fails when the rule is violated.
- Ensure the automation blocks regressions instead of only printing warnings.
- Cover at least one representative high-risk flow, component, or route.
- Keep thresholds or assertions in version control so changes remain reviewable.