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