You prompt AI to build a user registration endpointWhat is endpoint?A specific URL path on a server that handles a particular type of request, like GET /api/users. and a welcome email sender. Each works perfectly in isolation. You deploy, a user signs up, and nothing happens, no email, no error, just silence. The registration function returns a user object with id as a number, but the email function expects userId as a string. Both unit tests passed. The integration was never tested.
This is where AI-generated code breaks most often: at the boundaries between modules. AI builds each piece in isolation, with assumptions about data shapes, naming conventions, and error handling that do not match across modules.
What is integration testing?
Integration tests verify that multiple units work together correctly. Where unit tests mockWhat is mock?A fake replacement for a real dependency in tests that records how it was called so you can verify interactions. dependencies, integration tests use real (or realistic) ones.
Unit testWhat is unit test?An automated check that verifies a single function works correctly in isolation, with all external dependencies replaced by fakes. (isolated):
test('formatCurrency() formats correctly', () => {
expect(formatCurrency(1000)).toBe('CODE_BLOCK0.00');
});Integration testWhat is integration test?An automated check that verifies multiple parts of your application work together correctly, using real components instead of fakes. (multiple units):
test('cart displays formatted total', () => {
const cart = new Cart();
const product = { id: 1, price: 1000 };
cart.add(product);
// Tests Cart.add() + formatCurrency() working together
expect(cart.getTotalFormatted()).toBe('CODE_BLOCK0.00');
});The integration test catches bugs that exist in the space between units, mismatched data formats, incorrect function call order, missing error propagation.
Why integration bugs are AI's blind spot
When you ask AI to build a feature, it generates self-contained modules. Each moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions. has its own assumptions about input and output shapes. These assumptions are invisible because they live in the gap between prompts.
Here is a classic example:
// Module 1: parseUserInput (AI-generated)
function parseUserInput(raw) {
return raw.trim().toLowerCase();
}
// Module 2: validateEmail (AI-generated separately)
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}Both unit tests pass individually. But in the registration flow:
test('user can register with email', () => {
const input = ' USER@EXAMPLE.COM ';
const parsed = parseUserInput(input); // 'user@example.com'
const valid = validateEmail(input); // BUG: uses raw input, not parsed
expect(valid).toBe(true); // Fails - raw input has spaces
});The integration reveals that the composition is wrong. No unit testWhat is unit test?An automated check that verifies a single function works correctly in isolation, with all external dependencies replaced by fakes. could have caught this. When AI generates both modules, it does not reason about how they connect.
What to integration testWhat is integration test?An automated check that verifies multiple parts of your application work together correctly, using real components instead of fakes.
Focus on user-facing workflows and feature boundaries:
| Workflow | What to verify |
|---|---|
| User registration | Account creation, email sent, can login afterward |
| E-commerce checkout | Cart total, payment processed, order persisted, confirmation sent |
| Search and filter | Query parsed, filters applied, results sorted, pagination works |
| File upload | File received, validated, stored, metadata saved, user notified |
| Authentication | Login, token issued, protected routes accessible, token expired handling |
test('complete user registration flow', async () => {
const user = await registerUser({
email: 'test@example.com',
password: 'securePassword123'
});
expect(user.id).toBeDefined();
const email = await getLastEmail('test@example.com');
expect(email.subject).toContain('Welcome');
const session = await login({
email: 'test@example.com',
password: 'securePassword123'
});
expect(session.token).toBeDefined();
expect(session.user.email).toBe('test@example.com');
});This single test connects three systems: user creation, email delivery, and authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token.. A failure in any connection surfaces immediately.
Testing with databases
Integration tests often need a database. Use a separate test database with fresh state:
import { beforeEach, afterEach, test, expect } from 'vitest';
import { db } from './db';
beforeEach(async () => {
await db.migrate.latest();
});
afterEach(async () => {
await db.migrate.rollback();
});
test('user can create and retrieve a post', async () => {
const user = await db.users.create({
email: 'alice@example.com',
name: 'Alice'
});
const post = await db.posts.create({
userId: user.id,
title: 'Hello World',
content: 'My first post'
});
const retrieved = await db.posts.findById(post.id, {
include: { user: true }
});
expect(retrieved.title).toBe('Hello World');
expect(retrieved.user.name).toBe('Alice');
});Testing APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. endpoints
For backend APIs, tools like supertest let you test HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. endpoints without starting a real server:
import request from 'supertest';
import { app } from './app';
describe('Users API', () => {
test('POST /api/users creates user and returns 201', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'test@example.com', name: 'Alice' })
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(Number),
email: 'test@example.com',
name: 'Alice'
});
});
test('GET /api/users/:id returns 404 for missing user', async () => {
await request(app)
.get('/api/users/999999')
.expect(404);
});
});This tests your entire API stack: routing, middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it., controllers, database, and response formatting. When AI generates API handlers, the integration testWhat is integration test?An automated check that verifies multiple parts of your application work together correctly, using real components instead of fakes. is what catches the mismatch between what the handler returns and what the frontend expects.
Testing React components with context
Frontend integration tests verify that components interact correctly through shared state:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CartProvider } from './CartContext';
import ProductList from './ProductList';
import CartSummary from './CartSummary';
test('adding a product updates the cart summary', async () => {
render(
<CartProvider>
<ProductList />
<CartSummary />
</CartProvider>
);
await userEvent.click(screen.getByText('Add to Cart'));
expect(screen.getByText('1 item in cart')).toBeInTheDocument();
expect(screen.getByText('CODE_BLOCK0.00')).toBeInTheDocument();
});This verifies that ProductList, CartSummary, and CartProvider all communicate correctly through React context. When AI generates these components separately, this test catches the data flow bugs between them.
How much to mockWhat is mock?A fake replacement for a real dependency in tests that records how it was called so you can verify interactions. in integration tests
The whole point of integration tests is to use real dependencies. Over-mocking defeats the purpose.
| Mock this | Do not mock this |
|---|---|
| External email services (SendGrid, SES) | Internal business logic modules |
| Payment processors (Stripe, PayPal) | Your database (use test instance) |
| Third-party APIs you do not control | Your own API endpoints |
| Time-dependent operations (for determinism) | State management and data flow |
// Only mock truly external services
vi.mock('./email-service');
vi.mock('./payment-gateway');
// Use real database, real internal modules, real state managementvi.mock() calls than actual assertions, it is a unit test pretending to be an integration test.Real vs fake integration tests
Here is a quick way to tell if AI gave you a real integration testWhat is integration test?An automated check that verifies multiple parts of your application work together correctly, using real components instead of fakes. or a unit testWhat is unit test?An automated check that verifies a single function works correctly in isolation, with all external dependencies replaced by fakes. in disguise:
| Signal | Real integration test | Fake integration test |
|---|---|---|
| Dependencies | Uses real modules or test database | Every dependency is mocked |
| Data flow | Output of step 1 becomes input to step 2 | Each step uses hardcoded data |
| Assertions | Checks end result of the workflow | Checks that functions were called |
| Setup | Creates realistic preconditions | Mocks return whatever the test needs |
| Failure mode | Fails when module contracts break | Only fails if call order changes |
Integration testing best practices
Test both happy path and error cases. A checkout flow that works when payment succeeds must also handle payment decline gracefully, no orphaned orders, no charged-but-unfulfilled purchases.
Use setup and teardown hooks. beforeEach resets state so each test starts clean. afterEach cleans up resources so tests do not interfere with each other.
Keep tests independent. If test B depends on test A creating a user, test B will fail whenever test A fails. Each test should create its own preconditions.
Integration tests sit in the sweet spot of the testing pyramid: more realistic than unit tests, faster and more reliable than E2E testsWhat is e2e test?An automated check that drives the full application the way a user would, clicking buttons and filling forms in a real browser.. They catch the bugs that matter most, the ones that happen when AI-generated modules meet the real world.