Testing Types: Unit, Integration, and E2E
In this tutorial, you will learn about Testing Types: Unit, Integration, and E2E. We cover key concepts, practical examples, and best practices to help you master this topic.
API testing spans three levels: unit tests check individual functions, integration tests verify component interaction, and end-to-end tests validate complete workflows. Each level serves a different purpose in the quality assurance strategy.
What You'll Learn
- Unit Testing for API business logic
- Integration Testing for database and service interaction
- End-to-end testing for full system validation
- Mock vs real dependencies at each level
Why It Matters
The testing pyramid guides investment: many fast unit tests, fewer slower integration tests, and a handful of critical e2e tests. Proper balance catches bugs efficiently.
Real-World Use
Google's testing blog recommends 70% unit, 20% integration, 10% e2e. Stripe follows this model. GitHub's API tests are primarily integration tests with mocked external services.
flowchart TD
Unit[Unit Tests] --> Fast[Fast, No Network]
Unit --> Test[Test Business Logic]
Integration[Integration Tests] --> Medium[Medium Speed]
Integration --> Test2[Test API + DB + Services]
E2E[E2E Tests] --> Slow[Slow, Full Stack]
E2E --> Test3[Test Complete Workflows]
Teacher Mindset
Start with unit tests for validation and business logic. Add integration tests for each endpoint. Add e2e tests for critical user journeys. Mock external services at the unit level but use real dependencies at the e2e level.
Code Examples
// Example 1: Unit test for validation logic
describe('validateEmail', () => {
it('returns true for valid email', () => {
expect(validateEmail('user@example.com')).toBe(true);
});
it('returns false for invalid email', () => {
expect(validateEmail('not-an-email')).toBe(false);
});
});
// Example 2: Integration test with database
describe('POST /api/users', () => {
beforeEach(async () => {
await db.migrate.latest();
});
afterEach(async () => {
await db.migrate.rollback();
});
it('creates user in database', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@test.com' })
.expect(201);
const user = await db('users').where({ email: 'alice@test.com' }).first();
expect(user).toBeDefined();
expect(user.name).toBe('Alice');
});
});
// Example 3: E2E test for full workflow
describe('User registration and login', () => {
it('completes full registration workflow', async () => {
// Register
const register = await request(app)
.post('/api/auth/register')
.send({ name: 'Bob', email: 'bob@test.com', password: 'Pass123!' })
.expect(201);
const token = register.body.token;
// Access protected resource
const profile = await request(app)
.get('/api/users/me')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(profile.body.name).toBe('Bob');
});
});
Common Mistakes
- Writing integration tests that mock the database (defeats the purpose)
- Writing e2e tests for every edge case (too slow and brittle)
- Not having enough unit tests for business logic
- Using the same test data setup for all test levels
- Not cleaning up state between test runs
Practice
- Write a unit test for an input validation function.
- Write an integration test that hits the API and checks the database.
- Write an e2e test for a multi-step workflow.
- Refactor a slow e2e test into a faster integration test.
- Challenge: Measure test execution time for each level and calculate the ratio.
FAQ
Mini Project
Choose an API endpoint and write: a unit test for its validation logic, an integration test that calls the endpoint and checks the database, and an e2e test that exercises the complete user flow including the endpoint.
What's Next
Next, you will learn about Postman basics for manual and automated API testing.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro