Integration Testing
While Unit Tests prove that a single brick is strong, Integration Tests prove that the mortar (the glue) holds the bricks together to form a wall.
In a typical CodeHarborHub backend, this means testing if your Service Layer can successfully talk to your Database or an External API.
🧐 The "Why" Behind Integration Tests
You might have a perfectly working User object and a perfectly working Database. But if the User object expects a firstName and the Database table is named first_name, your app will crash.
Unit tests won't catch this. Integration tests will.
What Are We Testing?
In integration testing, we move beyond simple logic and start testing the "edges" of our application:
- Database Integration: Does my query actually return data from PostgreSQL?
- API Integration: Does my app correctly parse the JSON response from a payment gateway?
- File System: Can my app successfully write a PDF report to the
/uploadsfolder?
Setting Up the Environment
Because integration tests touch real systems, they are slower and more complex than unit tests. Here is the professional workflow we use:
- 🗄️ The Test Database
- 🔑 Environment Variables
Never run integration tests against your "Production" or "Development" database.
- Create a separate
test_db. - Run Migrations to set up the schema.
- Seed the database with "dummy" data.
- Wipe the data after the tests finish.
Use a .env.test file to point your app to the test database instead of the real one.
Example: Testing a User Service
Let's test if our UserService can actually save a user into the database using Prisma.
import { UserService } from '../services/userService';
import { prisma } from '../lib/prisma';
describe('UserService Integration', () => {
// Clean up the database before each test
beforeEach(async () => {
await prisma.user.deleteMany();
});
test('should successfully create a user in the database', async () => {
const userService = new UserService();
const userData = { name: 'Ajay', email: 'ajay@codeharborhub.com' };
// Act: Call the service that talks to the DB
const newUser = await userService.createUser(userData);
// Assert: Check if it's in the real DB
const dbUser = await prisma.user.findUnique({
where: { email: 'ajay@codeharborhub.com' }
});
expect(dbUser).toBeDefined();
expect(dbUser.name).toBe('Ajay');
});
});
Unit vs. Integration
| Feature | Unit Testing | Integration Testing |
|---|---|---|
| Scope | One function | Multiple modules |
| Dependencies | Mocked (Fake) | Real (DB, APIs) |
| Speed | Milliseconds | Seconds |
| Debugging | Easy (Know exactly where) | Harder (Could be the DB, Config, or Code) |
Summary Checklist
- I understand that integration tests check the "interaction" between modules.
- I know that I should use a dedicated Test Database.
- I understand that integration tests catch bugs that unit tests miss (like schema mismatches).
- I know how to use
beforeEachto keep my test database clean.
Because integration tests are slower, don't try to test every single "if/else" condition here. Use Unit Tests for the logic and Integration Tests just to ensure the connection works!