Integration Testing
In this page:
What Integration Tests Verify
Integration tests verify that multiple typed units — a service, a data layer, an API client — work correctly together, catching mismatches at their boundaries that isolated unit tests (which mock those boundaries away) can't see.
Example: What Integration Tests Verify
interface Repo { save(name: string): void; }
class Service {
constructor(private repo: Repo) {}
register(name: string) { this.repo.save(name); }
}
const repo: Repo = { save: (name) => console.log("Saved:", name) };
new Service(repo).register("Ravi");
Testing API Integration
Testing API integration means calling a real or realistically-typed HTTP client against a test server, verifying the response actually matches the TypeScript interface your code expects it to have.
Example: Testing API Integration
interface User { name: string; }
async function getUser(): Promise<User> {
const response = await fetch("data.php");
return response.json();
}
getUser().then((u) => console.log(u.name));
Database Integration
Database integration tests run against a real (often in-memory or containerized) database, confirming that your typed query results actually match the shapes your ORM or query layer declares them to be.
Example: Database Integration
interface Row { id: number; name: string; }
class InMemoryDb {
private rows: Row[] = [];
insert(row: Row) { this.rows.push(row); }
find(id: number) { return this.rows.find((r) => r.id === id); }
}
const db = new InMemoryDb();
db.insert({ id: 1, name: "Ravi" });
console.log(db.find(1));
Testing Complete Workflows
Testing a complete workflow — like "create user, then log in, then fetch profile" — exercises several typed modules in sequence, catching integration bugs that only appear when real data flows between them.
Example: Testing Complete Workflows
interface User { id: number; name: string; }
class UserFlow {
private users: User[] = [];
create(name: string): User {
const user = { id: this.users.length + 1, name };
this.users.push(user);
return user;
}
login(id: number) { return this.users.find((u) => u.id === id); }
}
const flow = new UserFlow();
const created = flow.create("Ravi");
console.log(flow.login(created.id));
Integration Test Isolation
Isolating integration tests from each other (fresh database state, no shared mutable fixtures) prevents one test's typed side effects from leaking into and breaking an unrelated test that runs after it.
Example: Integration Test Isolation
class TestDatabase {
private rows: string[] = [];
reset() { this.rows = []; }
insert(row: string) { this.rows.push(row); }
}
const db = new TestDatabase();
db.insert("test-data");
db.reset();
console.log(db);
- Mocking every dependency so the test no longer checks whether the parts work together.
- Sharing database state between tests, so one test's data breaks another.
- Testing against a live external API, making tests flaky and slow.
- Jest with
ts-jestlets you write and run tests in TypeScript. - Typed test utilities and typed mocks keep test code checked by the compiler.
- The same approach extends to testing React components and writing integration tests.
Chapter Quiz — Complete all 6 topics to unlock
0/6 topics done
Complete these topics first: