YouTip LogoYouTip

Ts Unit Testing

Unit testing practices in TypeScript projects to ensure code quality. Unit tests can verify the correctness of code. The TypeScript type system works perfectly with testing frameworks, allowing you to write type-safe test code. * * * * * * ## Why Unit Testing is Needed Unit testing is an important means of ensuring code quality. It can verify the correctness of code and prevent bugs from occurring. In TypeScript projects, test code also benefits from the type system: type errors are detected at compile time, IDEs provide intelligent hints, and test code becomes more reliable. Additionally, test code serves as the best documentation β€” you can understand the expected behavior of functions/classes through tests. > **Quality Assurance:** Unit tests can quickly identify regression issues, ensuring that code changes do not break existing functionality. * * * ## Test Framework Configuration Jest is the most popular testing framework for TypeScript projects. ## Installing Jest # Install Jest and related dependencies # - ts-jest: Allows Jest to run TypeScript files # -@types/jest: Type definitions for Jest npm install --save-dev jest ts-jest @types/jest # Initialize Jest configuration npx ts-jest config:init > **ts-jest:** This is a preprocessor that allows Jest to directly run TypeScript files without manual compilation. * * * ## Configuring jest.config.js Configure the Jest testing environment. ## jest.config.js module.exports={ // Use ts-jest preset preset:'ts-jest', // Test environment: node or browser testEnvironment:'node', // Directory for test files roots:['/src'], // Pattern for matching test files testMatch:['**/__tests__/**/*.ts'], // Supported file extensions moduleFileExtensions:['ts','js','json'], // Files to collect coverage from collectCoverageFrom:[ 'src/**/*.ts', '!src/**/*.d.ts'// Exclude type declaration files ] } > **Configuration Notes:** Test files are usually placed in the __tests__ directory or end with .test.ts. * * * ## Testing Functions First, write the business code to be tested. ## src/utils/calculator.ts // Calculator class export class Calculator { // Addition add(a: number, b: number): number { return a + b; } // Subtraction subtract(a: number, b: number): number { return a - b; } // Multiplication multiply(a: number, b: number): number { return a * b; } // Division divide(a: number, b: number): number { if(b ===0){ throw new Error("Cannot divide by zero"); } return a / b; } } Then write corresponding test code. ## src/utils/calculator.test.ts import{ Calculator } from "./calculator"; // Test suite: Tests for Calculator class describe("Calculator",()=>{ let calculator: Calculator; // Create a new Calculator instance before each test beforeEach(()=>{ calculator =new Calculator(); }); // Addition tests describe("add",()=>{ it("should add two numbers",()=>{ expect(calculator.add(2,3)).toBe(5); }); it("should handle negative numbers",()=>{ expect(calculator.add(-1,1)).toBe(0); }); }); // Division tests describe("divide",()=>{ it("should divide two numbers",()=>{ expect(calculator.divide(10,2)).toBe(5); }); it("should throw error when dividing by zero",()=>{ // Expect an error to be thrown expect(()=> calculator.divide(10,0)).toThrow(); }); }); }); **Output:** Calculator add βœ“ should add two numbers βœ“ should handle negative numbers divide βœ“ should divide two numbers βœ“ should throw error when dividing by zero > **describe/it:** describe is used to group tests, while it (or test) defines individual test cases. * * * ## Testing Services Testing business logic in the service layer. ## src/services/userService.ts // User type export interface User { id: number; name: string; } // User service class export class UserService { private users: User[]=[]; private nextId =1; // Create user createUser(name: string): User { const user ={ id:this.nextId++, name }; this.users.push(user); return user; } // Get user getUser(id: number): User |undefined{ return this.users.find(u => u.id=== id); } // Get all users getAllUsers(): User[]{ return[...this.users]; } } ## src/services/userService.test.ts import{ UserService } from "./userService"; describe("UserService",()=>{ let service: UserService; beforeEach(()=>{ service =new UserService(); }); describe("createUser",()=>{ it("should create a user with id",()=>{ const user = service.createUser("Alice"); expect(user.id).toBe(1); expect(user.name).toBe("Alice"); }); it("should increment id for each user",()=>{ const user1 = service.createUser("Alice"); const user2 = service.createUser("Bob"); expect(user2.id).toBe(user1.id+1); }); }); describe("getUser",()=>{ it("should return user by id",()=>{ const created = service.createUser("Alice"); const found = service.getUser(created.id); // Using optional chaining and toBe expect(found?.name).toBe("Alice"); }); it("should return undefined for non-existent id",()=>{ const found = service.getUser(999); expect(found).toBeUndefined(); }); }); }); **Output:** UserService createUser βœ“ should create a user with id βœ“ should increment id for each user getUser βœ“ should return user by id βœ“ should return undefined for non-existent id > **Test Isolation:** Each test case should be independent; use beforeEach to ensure each test has a clean state. * * * ## Mocking Use Mock to simulate dependencies such as external APIs, databases, etc. ## Example // Mock function: Create a mock function const mockCallback = jest.fn(x => x *2); // Use the mock function [1,2,3].forEach(mockCallback); // Verify function was called 3 times expect(mockCallback).toHaveBeenCalledTimes(3); // Verify arguments passed when function was called expect(mockCallback).toHaveBeenCalledWith(2); // Mock module: Simulate entire module jest.mock("./api",()=>({ fetchUser: jest.fn(()=> Promise.resolve({ id:1, name:"Alice"})) })); **Output:** βœ“ mock function called 3 times > **Purpose of Mocking:** When the code being tested depends on external systems, using Mock isolates these dependencies so only the target code logic is tested. * * * ## Considerations * **Test File Location:** Place in __tests__ directory or use .test.ts suffix * **Test Naming:** Use descriptive test names that explain expected behavior * **Independent Tests:** Each test should run independently without relying on others * **Coverage:** Focus on core business logic test coverage > **Best Practices:** Tests should be fast, reliable, and independent. Follow the AAA principle: Arrange (Setup), Act (Execution), Assert (Assertion). * * * ## Summary Unit testing is an essential approach to ensure TypeScript code quality. * **Jest:** The most popular TypeScript testing framework * **describe:** Used to group related tests * **it/test:** Defines individual test cases * **expect:** Asserts test results * **Mock:** Simulates external dependencies > **Recommendation:** Write tests for key business logic to ensure that code changes don't introduce bugs.
← Ts Indexed TypesTs Template Literal β†’