📖 Table of Contents
- Introduction to Unit Testing
- Why Unit Testing is Important
- Setting Up the Testing Environment
- Installing Testing Dependencies
- Project Structure for Testing
- Writing Unit Tests for Controllers
- Writing Unit Tests for Middleware
- Writing Unit Tests for Services
- Running Unit Tests
- Best Practices for Unit Testing
- Conclusion
1. Introduction to Unit Testing
Unit testing involves testing individual units or components of an application in isolation to ensure they work as expected. In Express.js, unit tests typically focus on testing controllers, middlewares, and services.
2. Why Unit Testing is Important
- Detects bugs early in development.
- Ensures code behaves as expected.
- Makes refactoring easier.
- Improves code maintainability.
- Provides documentation for how the code should work.
3. Setting Up the Testing Environment
Ensure your Express.js application is set up and functional.
If not, follow the Setup Guide.
4. Installing Testing Dependencies
Install the required testing libraries:
npm install --save-dev jest ts-jest @types/jest supertest @types/supertest
- jest: Testing framework.
- ts-jest: Jest configuration for TypeScript.
- supertest: Library for testing HTTP APIs.
4.1 Configure Jest
Create a jest.config.ts
file in your project root:
export default {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
moduleFileExtensions: ['ts', 'js', 'json'],
testMatch: ['**/__tests__/**/*.test.ts'],
};
Add a test script to your package.json
:
{
"scripts": {
"test": "jest --coverage"
}
}
5. Project Structure for Testing
src/
├── controllers/
│ ├── userController.ts
├── services/
│ ├── userService.ts
├── middleware/
│ ├── authMiddleware.ts
└── __tests__/
├── controllers/
│ ├── userController.test.ts
├── services/
│ ├── userService.test.ts
├── middleware/
├── authMiddleware.test.ts
6. Writing Unit Tests for Controllers
6.1 Example Controller
File: src/controllers/userController.ts
import { Request, Response } from 'express';
export const getUser = (req: Request, res: Response) => {
res.status(200).json({ id: 1, name: 'John Doe' });
};
6.2 Test Controller
File: src/__tests__/controllers/userController.test.ts
import { getUser } from '../../controllers/userController';
import { Request, Response } from 'express';
describe('User Controller - getUser', () => {
it('should return user data with status 200', () => {
const req = {} as Request;
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
} as unknown as Response;
getUser(req, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({ id: 1, name: 'John Doe' });
});
});
Explanation:
- Mocked
Request
andResponse
. - Called the
getUser
controller function. - Asserted status code and response data.
7. Writing Unit Tests for Middleware
7.1 Example Middleware
File: src/middleware/authMiddleware.ts
import { Request, Response, NextFunction } from 'express';
export const isAuthenticated = (req: Request, res: Response, next: NextFunction) => {
if (req.headers.authorization === 'Bearer validToken') {
next();
} else {
res.status(403).json({ message: 'Forbidden' });
}
};
7.2 Test Middleware
File: src/__tests__/middleware/authMiddleware.test.ts
import { isAuthenticated } from '../../middleware/authMiddleware';
import { Request, Response, NextFunction } from 'express';
describe('Auth Middleware - isAuthenticated', () => {
it('should call next if token is valid', () => {
const req = { headers: { authorization: 'Bearer validToken' } } as Request;
const res = {} as Response;
const next = jest.fn();
isAuthenticated(req, res, next);
expect(next).toHaveBeenCalled();
});
it('should return 403 if token is invalid', () => {
const req = { headers: { authorization: '' } } as Request;
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
} as unknown as Response;
const next = jest.fn();
isAuthenticated(req, res, next);
expect(res.status).toHaveBeenCalledWith(403);
expect(res.json).toHaveBeenCalledWith({ message: 'Forbidden' });
});
});
8. Writing Unit Tests for Services
8.1 Example Service
File: src/services/userService.ts
export const findUserById = (id: number) => {
if (id === 1) {
return { id: 1, name: 'John Doe' };
}
return null;
};
8.2 Test Service
File: src/__tests__/services/userService.test.ts
import { findUserById } from '../../services/userService';
describe('User Service - findUserById', () => {
it('should return user data when valid ID is provided', () => {
const user = findUserById(1);
expect(user).toEqual({ id: 1, name: 'John Doe' });
});
it('should return null when invalid ID is provided', () => {
const user = findUserById(2);
expect(user).toBeNull();
});
});
9. Running Unit Tests
Run your tests with:
npm run test
Sample Output:
PASS __tests__/controllers/userController.test.ts
PASS __tests__/middleware/authMiddleware.test.ts
PASS __tests__/services/userService.test.ts
Test Suites: 3 passed, 3 total
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: 2.345 s
Check Test Coverage
npm run test -- --coverage
10. Best Practices for Unit Testing
- Test one functionality per test case.
- Mock external dependencies (e.g., database calls).
- Ensure your tests are independent and isolated.
- Use descriptive
describe
andit
blocks. - Avoid testing implementation details.
- Include edge cases in your tests.
11. Conclusion
- Unit testing validates each part of your Express.js app independently.
- Focus on Controllers, Middleware, and Services.
- Use Jest and Supertest for efficient testing.
✨ Your Express.js application now has comprehensive unit tests! 🚀
Author: Mohin Sheikh
Follow me on GitHub for more insights!
Top comments (0)