stackademic

The leading education platform for anyone with an interest in software development.

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