Integrating, Testing and Debugging the Capstone

Integration is where most capstone projects stall. Each layer works in isolation โ€” the Express API passes Postman tests, the React components render correctly in Storybook โ€” but when they connect to each other, things break in ways that did not appear during isolated development. CORS errors, environment variable mismatches, wrong API response shapes, missing auth headers โ€” these are the bugs that integration tests exist to catch early. In this lesson you will systematically connect the React frontend to the Express API, run the full test suites, write E2E tests for the critical flows, and work through the most common integration problems with a debugging methodology that applies to any MERN project.

Integration Debugging Methodology

When a feature does not work end-to-end, isolate the layer:

Step 1: Is the API working? (Postman)
  โ†’ Call the endpoint directly in Postman
  โ†’ If it fails: Express bug โ€” fix in the server layer
  โ†’ If it succeeds: the problem is in React or the connection

Step 2: Is the React request reaching the API? (Browser DevTools)
  โ†’ Open Network tab โ†’ trigger the action โ†’ find the XHR/Fetch request
  โ†’ If no request: Axios call not made โ€” React bug
  โ†’ If request shows "CORS error": fix CORS on Express or proxy
  โ†’ If request reaches server but wrong data: check request body and headers

Step 3: Is the response correct? (Browser DevTools โ†’ Response tab)
  โ†’ 401? Check Authorization header โ€” is the token being sent?
  โ†’ 404? Check the URL โ€” does it match the Express route?
  โ†’ 422/400? Check the request body โ€” required fields missing?
  โ†’ 200 but wrong data? Check that populate() is called on the right fields

Step 4: Is React using the response correctly?
  โ†’ console.log(res.data) โ€” does the shape match what the component expects?
  โ†’ Is it res.data.data or res.data? Check the Express response envelope
Note: The browser DevTools Network tab is your most important integration debugging tool. Every API call made by Axios is visible there โ€” you can see the request URL, headers (including the Authorization header), request body, response status, and response body. Before spending time adding console.logs to React components, check the Network tab first. The answer to most integration bugs is visible there within 30 seconds.
Tip: Configure the Vite proxy for development so React API calls go to /api/... and Vite forwards them to Express at localhost:5000. This avoids CORS in development entirely โ€” from the browser’s perspective, the React app and the API are on the same origin. In production, Axios uses VITE_API_URL which points to Render. This two-environment setup is the standard MERN development configuration.
Warning: Test the auth flow first before testing any other feature. If AuthContext.loading never becomes false, ProtectedRoute redirects all authenticated pages to login โ€” making every other feature appear broken. If the token is not being attached to requests, all protected API calls return 401 โ€” appearing as if controllers have bugs when the issue is the Axios interceptor. Always verify auth before debugging other features.

Vite Proxy Configuration

// client/vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path  from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': path.resolve(__dirname, './src') },
  },
  server: {
    proxy: {
      '/api': {
        target:      'http://localhost:5000',
        changeOrigin: true,
        secure:       false,
        // All /api/* requests are forwarded to Express at localhost:5000
        // The browser sees them as same-origin โ€” no CORS issues in dev
      },
    },
  },
  test: {
    globals:     true,
    environment: 'jsdom',
    setupFiles:  ['./src/test/setup.js'],
  },
});

Common Integration Problems and Fixes

Symptom Likely Cause Fix
CORS error in browser Client URL not in Express CORS allowlist Add CLIENT_URL to Express CORS, redeploy
401 on every API call Token not in Authorization header Verify Axios interceptor reads from localStorage
ProtectedRoute redirects when logged in AuthContext.loading never false Verify /api/auth/me resolves; check MONGODB_URI
res.data.data is undefined Express returns res.json({ data: post }) not { success, data } Standardise all Express responses to { success, data }
Socket.io not connecting Socket.io CORS not configured Set cors: { origin: CLIENT_URL } on new Server()
File upload fails Content-Type manually set on Axios Remove manual Content-Type โ€” Axios sets boundary automatically
Post body renders as plain text dangerouslySetInnerHTML not used Use dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(body) }}

Running the Full Test Suite

# Server integration tests
cd server
npm test -- --forceExit
# Expected: all auth, post, comment, user routes pass

# Client component tests
cd ../client
npm test -- --run
# Expected: PostCard, FormField, LoginPage tests pass

# E2E tests (with both servers running)
cd ..
npx playwright test --headed  # see the browser
# Expected: register, login, create post flows pass

Playwright E2E โ€” Critical Flows

// e2e/full-flow.spec.js โ€” the complete new-user-to-published-post flow
import { test, expect } from '@playwright/test';

test('new user registers, creates a post, and views it', async ({ page }) => {
  const email = `capstone-${Date.now()}@example.com`;
  const password = 'TestPass@1234';

  // โ”€โ”€ Register โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  await page.goto('/register');
  await page.getByLabel('Name').fill('Capstone Author');
  await page.getByLabel('Email').fill(email);
  await page.getByLabel('Password').fill(password);
  await page.getByLabel('Confirm Password').fill(password);
  await page.getByRole('button', { name: /create account/i }).click();
  await expect(page).toHaveURL(/\/dashboard/);

  // โ”€โ”€ Create Post โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  await page.goto('/posts/new');
  await page.getByLabel('Title').fill('My Capstone Post');
  await page.getByLabel('Content').fill(
    'This is the body of my capstone test post with enough words to pass validation.'
  );
  await page.getByLabel('Publish immediately').check();
  await page.getByRole('button', { name: /publish/i }).click();

  // Should redirect to the new post
  await expect(page).toHaveURL(/\/posts\//);
  await expect(page.getByRole('heading', { name: 'My Capstone Post' })).toBeVisible();

  // โ”€โ”€ View on Home Page โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  await page.goto('/');
  await expect(page.getByText('My Capstone Post')).toBeVisible();

  // โ”€โ”€ Logout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  await page.getByRole('button', { name: /log out/i }).click();
  await page.goto('/dashboard');
  await expect(page).toHaveURL(/\/login/); // redirected โ€” no longer authenticated
});

Common Mistakes

Mistake 1 โ€” Testing without the servers running

โŒ Wrong โ€” Playwright tests fail with connection refused:

Error: connect ECONNREFUSED 127.0.0.1:5173
# React dev server is not running

โœ… Correct โ€” use Playwright’s webServer config to auto-start servers, or manually start both before running tests.

Mistake 2 โ€” Not resetting test data between E2E tests

โŒ Wrong โ€” second test fails because the email from the first test is already registered:

await page.getByLabel('Email').fill('test@example.com'); // used in 3 tests
// Second test: 409 Conflict โ€” email already registered

โœ… Correct โ€” use unique emails with timestamps: `test-${Date.now()}@example.com`.

Mistake 3 โ€” Skipping the Network tab and guessing at bugs

โŒ Wrong โ€” adding console.logs to every React component without first checking DevTools.

โœ… Correct โ€” open Network tab, reproduce the bug, read the failing request and response. The cause is visible in 30 seconds 90% of the time.

Quick Reference โ€” Debugging Checklist

Check Where to Look
Request URL correct DevTools Network โ†’ Request URL
Auth header present DevTools Network โ†’ Headers โ†’ Authorization
Response shape correct DevTools Network โ†’ Response tab
Server error details Render logs (or terminal in dev)
React state value React DevTools โ†’ Components tab
Socket connection status Console: socket.connected

🧠 Test Yourself

Your React PostDetailPage shows “Failed to load” for every post, but Postman successfully fetches posts from GET http://localhost:5000/api/posts/:id. What should you check first in the browser?