Integration Tests

Verifying that multiple units work correctly together across real boundaries

Overview

An integration test checks that several parts of your system work together correctly, such as a route handler talking to a service and a database. Unit tests can all pass while the app is still broken because the pieces don't connect properly; integration tests catch those seams. They are slower than unit tests but give more realistic confidence.

Syntax / Usage

Integration tests exercise a real boundary. Here a Supabase-style query function is tested against an in-memory test client so the call path is real but repeatable.

// userRepo.js
async function getUserByEmail(db, email) {
  const { data, error } = await db
    .from("users")
    .select("*")
    .eq("email", email)
    .single();
  if (error) throw error;
  return data;
}
module.exports = { getUserByEmail };

// userRepo.test.js
const { createTestDb } = require("./testDb");
const { getUserByEmail } = require("./userRepo");

describe("getUserByEmail", () => {
  let db;
  beforeEach(async () => {
    db = await createTestDb();
    await db.from("users").insert({ email: "ada@example.com", name: "Ada" });
  });

  test("returns the matching user row", async () => {
    const user = await getUserByEmail(db, "ada@example.com");
    expect(user.name).toBe("Ada");
  });
});

Examples

Testing an HTTP endpoint end to end within the app process:

const request = require("supertest");
const app = require("./app");

test("GET /health returns ok", async () => {
  const res = await request(app).get("/health");
  expect(res.status).toBe(200);
  expect(res.body).toEqual({ status: "ok" });
});

Verifying that a write is actually persisted and readable:

test("created user can be fetched back", async () => {
  await request(app).post("/users").send({ email: "grace@example.com" });
  const res = await request(app).get("/users/grace@example.com");
  expect(res.body.email).toBe("grace@example.com");
});

Common Mistakes

  • Sharing one database between tests so leftover data makes them flaky
  • Forgetting to reset or seed state in beforeEach, causing order-dependent failures
  • Mocking so much that nothing real is actually integrated
  • Hitting live third-party services, making tests slow and unreliable
  • Not awaiting async calls, so assertions run before the work finishes

See Also

testing-unit-tests testing-end-to-end testing-mocking