Introduction: Why Test TypeScript Code?
TypeScript is a superset of JavaScript that adds static typing. However, types do not guarantee correct application logic. Testing in TypeScript is not just a "good practice" but a necessity for maintaining code quality, preventing regressions, and accelerating development.
In this article, we will cover three main levels of testing: unit, integration, and e2e (end-to-end). You will learn which tools to use, see code examples, and understand how to build a balanced testing strategy for your TypeScript project.
1. Unit Testing in TypeScript
What are Unit Tests?
Unit tests check isolated modules or functions. They run quickly and do not require external dependencies (databases, network, file system). The main goal is to ensure that every small unit of code works correctly.
Tools: Jest + ts-jest
The most popular framework for unit testing in TypeScript is Jest with the ts-jest transformer. It supports mocks, spies, coverage, and integrates well with TypeScript.
Unit Test Example
Suppose we have a function for calculating a discount:
// discount.tsexport function calculateDiscount(price: number, discountPercent: number): number { if (price < 0 || discountPercent < 0 || discountPercent > 100) { throw new Error('Invalid input'); } return price * (1 - discountPercent / 100);}Writing the test:
// discount.test.tsimport { calculateDiscount } from './discount';
describe('calculateDiscount', () => { it('should apply 10% discount correctly', () => { expect(calculateDiscount(1000, 10)).toBe(900); });
it('should throw error for negative price', () => { expect(() => calculateDiscount(-100, 10)).toThrow('Invalid input'); });
it('should handle 0% discount', () => { expect(calculateDiscount(500, 0)).toBe(500); });});Run with: npx jest. Jest will automatically find all *.test.ts files.
Best Practices for Unit Tests
- Isolate dependencies — use mocks (jest.mock) for modules that access the database or API.
- Test edge cases — empty arrays, null, negative numbers.
- Follow the FIRST principle — Fast, Isolated, Repeatable, Self-validating, Timely.
- Use describe/it — for a readable test structure.
2. Integration Testing
What do Integration Tests Check?
Integration tests check the interaction of several modules: controller + service + database, or API endpoint + middleware. They are slower than unit tests but provide more confidence in the correct coupling of components.
Tools: Supertest + Jest + in-memory DB
For testing Express/Node.js APIs, Supertest (HTTP assertions) is often used together with Jest. The database can be run in-memory (e.g., mongodb-memory-server or sqlite3).
Integration Test Example for Express API
Suppose we have a simple REST API for managing users:
// app.tsimport express from 'express';import { userRouter } from './routes/userRoutes';
const app = express();app.use(express.json());app.use('/api/users', userRouter);
export { app };Test with Supertest:
// user.integration.test.tsimport request from 'supertest';import { app } from '../app';
describe('POST /api/users', () => { it('should create a new user and return 201', async () => {