Frontend Engineering/
Lesson

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.

02

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.

AI pitfall
When you ask AI to generate tests for a multi-step workflow, it often tests each step independently within the same test file, never actually connecting the output of one step to the input of the next. Look for tests where each step uses hardcoded data instead of passing results forward, those are fake integration tests.
03

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:

WorkflowWhat to verify
User registrationAccount creation, email sent, can login afterward
E-commerce checkoutCart total, payment processed, order persisted, confirmation sent
Search and filterQuery parsed, filters applied, results sorted, pagination works
File uploadFile received, validated, stored, metadata saved, user notified
AuthenticationLogin, 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.

04

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');
});
Never run integration tests against a production database. Always use a test instance with disposable data that resets between runs.
05

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.

06

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.

07

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 thisDo 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 controlYour 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 management
AI pitfall
When AI generates integration tests, it often mocks the database with hardcoded return values. This means your test verifies that your code handles fake data correctly, but never checks that your SQL queries, ORM calls, or data transformations actually work. If the test file has more vi.mock() calls than actual assertions, it is a unit test pretending to be an integration test.
08

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:

SignalReal integration testFake integration test
DependenciesUses real modules or test databaseEvery dependency is mocked
Data flowOutput of step 1 becomes input to step 2Each step uses hardcoded data
AssertionsChecks end result of the workflowChecks that functions were called
SetupCreates realistic preconditionsMocks return whatever the test needs
Failure modeFails when module contracts breakOnly fails if call order changes
09

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.

AI pitfall
AI-generated integration tests frequently share mutable state between test cases. The AI creates a user in the first test, then references that user in the second test. This creates hidden dependencies, if tests run in a different order or in parallel, they fail mysteriously. Each test must set up its own data from scratch.

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.