Testing in TypeScript: unit, integration, e2e from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why testing in TypeScript is a must have

TypeScript is a powerful tool that adds static typing to JavaScript. However, even the strictest type system does not guarantee that your code will work correctly at runtime. Bugs, logical errors, incorrect handling of edge cases — all of this remains outside the scope of compilation. This is where testing comes to the rescue.

In this article, we will cover three main levels of testing in TypeScript: unit, integration, and e2e (end-to-end). You will learn how they differ, when to use which, and see real code examples. After reading, you will be able to set up a complete test pipeline for your TypeScript project.



1. Unit testing: checking atomic units of code

Unit tests are the fastest and cheapest level of verification. They isolate a single function, method, or class and test it in isolation from external dependencies (databases, APIs, file system).



1.1 Choosing a tool and setup

The most popular framework for TypeScript is Jest. It has built-in support for TypeScript via ts-jest or @swc/jest.

// Installationnpm install --save-dev jest @types/jest ts-jest

// Initialize configurationnpx ts-jest config:init

After this, a jest.config.js file with settings will appear in the root directory.



1.2 Example of a unit test for a function

Let's say we have a simple function for calculating a discount:

// discount.tsexport function applyDiscount(price: number, discountPercent: number): number {  if (discountPercent < 0 || discountPercent > 100) {    throw new Error('Invalid discount percentage');  }  return price - (price * discountPercent) / 100;}

Let's write a unit test for it:

// discount.test.tsimport { applyDiscount } from './discount';

describe('applyDiscount', () => { it('should correctly calculate a 10% discount', () => { expect(applyDiscount(1000, 10)).toBe(900); });

it('should return the original price with a 0% discount', () => { expect(applyDiscount(500, 0)).toBe(500); });

it('should throw an error for a discount greater than 100%', () => { expect(() => applyDiscount(100, 150)).toThrow('Invalid discount percentage'); });});

Run: npx jest. All three tests should pass green.



1.3 Mocks and stubs

If a function accesses an external service (e.g., a database), we replace it with a mock:

// userService.test.tsimport { getUser } from './userService';import { db } from './database';

jest.mock('./database'); // Automatic mock

test('getUser returns a user by ID', async () => { (db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Ivan' }); const user = await getUser(1); expect(user.name).toBe('Ivan');});


2. Integration testing: checking module interaction

Integration tests check how different parts of the system work together: controllers, services, repositories, databases. They are slower than unit tests but provide more confidence in the correctness of the connections.



2.1 Testing an API endpoint with Express

For testing HTTP requests, we use the Supertest library together with Jest.

// Installationnpm install --save-dev supertest @types/supertest

Let's assume we have an Express application:

// app.tsimport express from 'express';import { createUser } from './controllers/userController';

export const app = express();app.use(express.json());app.post('/api/users', createUser);

Integration test:

// app.test.tsimport request from 'supertest';import { app } from './                

Blogs

Book Recommendations