+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 99 of 355

πŸ§ͺ Jest with TypeScript: Test Runner Setup Mastery

Master Jest configuration with TypeScript for bulletproof testing, complete type safety, and professional test automation πŸš€

πŸš€Intermediate
20 min read

Prerequisites

  • Basic TypeScript syntax and types πŸ“
  • Understanding of JavaScript testing concepts πŸ§ͺ
  • Node.js and npm/pnpm basics πŸ“¦

What you'll learn

  • Configure Jest for TypeScript with complete type safety πŸ›‘οΈ
  • Write type-safe unit tests with intelligent autocomplete ✨
  • Set up advanced Jest features like mocking and coverage πŸ“Š
  • Create a professional testing workflow and automation πŸ”„

🎯 Introduction

Welcome to the world of professional TypeScript testing with Jest! πŸŽ‰ In this guide, we’ll transform you from a testing novice into a Jest master who writes bulletproof tests with complete type safety.

You’ll discover how to configure Jest to work seamlessly with TypeScript, enabling you to catch bugs before they reach production πŸ›. Whether you’re building React applications 🌐, Node.js APIs πŸ–₯️, or utility libraries πŸ“š, mastering Jest with TypeScript is essential for maintaining high-quality, reliable codebases.

By the end of this tutorial, you’ll be writing tests that are not just functional, but elegantly typed and impossible to break! πŸš€ Let’s dive in! πŸŠβ€β™‚οΈ

πŸ“š Understanding Jest with TypeScript

πŸ€” What is Jest?

Jest is like a Swiss Army knife for JavaScript testing πŸ”§. Think of it as your personal quality assurance team that works 24/7 to ensure your code behaves exactly as expected. It’s the most popular testing framework for JavaScript and TypeScript projects.

In TypeScript terms, Jest provides:

  • ✨ Zero-config setup - works out of the box with sensible defaults
  • πŸš€ Built-in assertions - comprehensive expect() API for all test scenarios
  • πŸ›‘οΈ Mocking capabilities - isolate units under test from dependencies
  • πŸ“Š Code coverage - measure how much of your code is tested

πŸ’‘ Why Use Jest with TypeScript?

Here’s why this combination is powerful:

  1. Type Safety in Tests πŸ”’: Catch test errors at compile time
  2. IntelliSense Support 🧠: Get autocomplete for your test APIs
  3. Refactoring Confidence πŸ”§: Change code without breaking tests
  4. Professional Workflows πŸ‘”: Industry-standard testing practices
  5. Team Collaboration πŸ‘₯: Self-documenting test specifications

Real-world example: When testing a user authentication system πŸ”, Jest with TypeScript ensures your test data matches your User interface, preventing runtime surprises!

πŸ”§ Jest Installation and Setup

πŸ“¦ Installing Jest Dependencies

Let’s set up Jest for TypeScript step by step:

# πŸ“¦ Install Jest and TypeScript support
pnpm add -D jest @types/jest ts-jest

# πŸ› οΈ For React projects, also add:
pnpm add -D @testing-library/react @testing-library/jest-dom

# 🎯 For Node.js projects, consider:
pnpm add -D supertest @types/supertest

βš™οΈ Jest Configuration (jest.config.js)

// πŸ“ jest.config.js - Complete Jest configuration
const config = {
  // 🎯 Use ts-jest preset for TypeScript support
  preset: 'ts-jest',
  
  // 🌍 Test environment - 'node' for backend, 'jsdom' for frontend
  testEnvironment: 'node', // or 'jsdom' for React apps
  
  // πŸ“ Root directory for tests and modules
  rootDir: 'src',
  
  // πŸ” Test file patterns
  testMatch: [
    '**/__tests__/**/*.test.ts',
    '**/__tests__/**/*.spec.ts',
    '**/*.test.ts',
    '**/*.spec.ts'
  ],
  
  // πŸ“Š Collect coverage from these files
  collectCoverageFrom: [
    '**/*.{ts,tsx}',
    '!**/*.d.ts',
    '!**/node_modules/**',
    '!**/__tests__/**'
  ],
  
  // 🎨 Coverage reporting
  coverageReporters: ['text', 'lcov', 'html'],
  
  // 🚫 Coverage thresholds (fail if below)
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  
  // πŸ› οΈ Setup files run before each test
  setupFilesAfterEnv: ['<rootDir>/__tests__/setup.ts'],
  
  // πŸ—‚οΈ Module path mapping (match your tsconfig.json)
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/$1',
    '^@components/(.*)$': '<rootDir>/components/$1',
    '^@utils/(.*)$': '<rootDir>/utils/$1'
  },
  
  // πŸ”„ Transform configuration
  transform: {
    '^.+\\.tsx?$': ['ts-jest', {
      // πŸš€ Speed up compilation
      isolatedModules: true,
      // πŸ“ Use project's TypeScript config
      tsconfig: 'tsconfig.json'
    }]
  },
  
  // πŸ“„ File extensions to consider
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
  
  // πŸš€ Optimize test performance
  maxWorkers: '50%',
  
  // 🎭 Clear mocks between tests
  clearMocks: true,
  
  // πŸ“‹ Verbose output for debugging
  verbose: true
};

module.exports = config;

πŸ“ TypeScript Configuration Updates

// πŸ“ tsconfig.json - Update for Jest compatibility
{
  "compilerOptions": {
    "target": "es2020",
    "module": "esnext",
    "lib": ["es2020", "dom"],
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    
    // 🎯 Path mapping for cleaner imports
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"],
      "@types/*": ["src/types/*"]
    },
    
    // πŸ§ͺ Include Jest types
    "types": ["jest", "@testing-library/jest-dom"]
  },
  "include": [
    "src/**/*",
    "**/*.test.ts",
    "**/*.spec.ts"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "coverage"
  ]
}

πŸ§ͺ Writing Your First TypeScript Tests

πŸ“ Basic Test Structure

// πŸ“ __tests__/setup.ts - Global test setup
import '@testing-library/jest-dom';

// 🎯 Global test configuration
beforeAll(() => {
  console.log('πŸš€ Starting test suite...');
});

afterAll(() => {
  console.log('βœ… Test suite completed!');
});

// 🧹 Clean up after each test
afterEach(() => {
  jest.clearAllMocks();
});
// πŸ“ utils/math.ts - Code to test
export interface CalculationResult {
  value: number;
  operation: string;
  timestamp: Date;
}

export class Calculator {
  private history: CalculationResult[] = [];
  
  // βž• Addition with history tracking
  add(a: number, b: number): CalculationResult {
    const result: CalculationResult = {
      value: a + b,
      operation: `${a} + ${b}`,
      timestamp: new Date()
    };
    
    this.history.push(result);
    return result;
  }
  
  // βœ–οΈ Multiplication with validation
  multiply(a: number, b: number): CalculationResult {
    if (!Number.isFinite(a) || !Number.isFinite(b)) {
      throw new Error('Invalid input: numbers must be finite 🚫');
    }
    
    const result: CalculationResult = {
      value: a * b,
      operation: `${a} Γ— ${b}`,
      timestamp: new Date()
    };
    
    this.history.push(result);
    return result;
  }
  
  // πŸ“Š Get calculation history
  getHistory(): CalculationResult[] {
    return [...this.history]; // πŸ“‹ Return copy to prevent mutation
  }
  
  // 🧹 Clear calculation history
  clearHistory(): void {
    this.history = [];
  }
}

// 🎯 Utility function for formatting
export const formatResult = (result: CalculationResult): string => {
  return `${result.operation} = ${result.value}`;
};

// ⏰ Async operation simulation
export const calculateAsync = async (
  operation: (a: number, b: number) => number,
  a: number,
  b: number,
  delay: number = 100
): Promise<number> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(operation(a, b));
    }, delay);
  });
};

βœ… Comprehensive Test Suite

// πŸ“ __tests__/math.test.ts - Complete test suite
import { Calculator, formatResult, calculateAsync, type CalculationResult } from '../utils/math';

// 🎯 Group related tests
describe('Calculator Class', () => {
  let calculator: Calculator;
  
  // πŸ”„ Fresh instance for each test
  beforeEach(() => {
    calculator = new Calculator();
  });
  
  // βž• Addition tests
  describe('add method', () => {
    it('should add two positive numbers correctly', () => {
      // 🎯 Arrange
      const a = 5;
      const b = 3;
      
      // 🎬 Act
      const result = calculator.add(a, b);
      
      // βœ… Assert
      expect(result.value).toBe(8);
      expect(result.operation).toBe('5 + 3');
      expect(result.timestamp).toBeInstanceOf(Date);
    });
    
    it('should handle negative numbers', () => {
      const result = calculator.add(-5, 3);
      
      expect(result.value).toBe(-2);
      expect(result.operation).toBe('-5 + 3');
    });
    
    it('should handle decimal numbers', () => {
      const result = calculator.add(0.1, 0.2);
      
      // 🎯 Use toBeCloseTo for floating point comparisons
      expect(result.value).toBeCloseTo(0.3);
    });
  });
  
  // βœ–οΈ Multiplication tests
  describe('multiply method', () => {
    it('should multiply two numbers correctly', () => {
      const result = calculator.multiply(4, 5);
      
      expect(result.value).toBe(20);
      expect(result.operation).toBe('4 Γ— 5');
    });
    
    it('should throw error for invalid inputs', () => {
      // 🚫 Test error cases
      expect(() => calculator.multiply(Infinity, 5)).toThrow('Invalid input: numbers must be finite 🚫');
      expect(() => calculator.multiply(5, NaN)).toThrow('Invalid input: numbers must be finite 🚫');
    });
    
    it('should handle zero multiplication', () => {
      const result = calculator.multiply(5, 0);
      
      expect(result.value).toBe(0);
    });
  });
  
  // πŸ“Š History tracking tests
  describe('history functionality', () => {
    it('should track calculation history', () => {
      calculator.add(1, 2);
      calculator.multiply(3, 4);
      
      const history = calculator.getHistory();
      
      expect(history).toHaveLength(2);
      expect(history[0].operation).toBe('1 + 2');
      expect(history[1].operation).toBe('3 Γ— 4');
    });
    
    it('should return copy of history (immutability)', () => {
      calculator.add(1, 2);
      
      const history1 = calculator.getHistory();
      const history2 = calculator.getHistory();
      
      // πŸ“‹ Should be different arrays with same content
      expect(history1).not.toBe(history2);
      expect(history1).toEqual(history2);
    });
    
    it('should clear history correctly', () => {
      calculator.add(1, 2);
      calculator.multiply(3, 4);
      
      expect(calculator.getHistory()).toHaveLength(2);
      
      calculator.clearHistory();
      
      expect(calculator.getHistory()).toHaveLength(0);
    });
  });
});

// 🎨 Utility function tests
describe('formatResult function', () => {
  it('should format result correctly', () => {
    const mockResult: CalculationResult = {
      value: 10,
      operation: '5 + 5',
      timestamp: new Date()
    };
    
    const formatted = formatResult(mockResult);
    
    expect(formatted).toBe('5 + 5 = 10');
  });
});

// ⏰ Async function tests
describe('calculateAsync function', () => {
  it('should perform async calculation', async () => {
    const addOperation = (a: number, b: number) => a + b;
    
    // 🎯 Test async operation
    const result = await calculateAsync(addOperation, 5, 3, 50);
    
    expect(result).toBe(8);
  });
  
  it('should handle custom delay', async () => {
    const multiplyOperation = (a: number, b: number) => a * b;
    
    const startTime = Date.now();
    await calculateAsync(multiplyOperation, 2, 3, 100);
    const endTime = Date.now();
    
    // ⏱️ Should take at least 100ms
    expect(endTime - startTime).toBeGreaterThanOrEqual(100);
  });
  
  it('should work with different operations', async () => {
    const divideOperation = (a: number, b: number) => a / b;
    
    const result = await calculateAsync(divideOperation, 10, 2);
    
    expect(result).toBe(5);
  });
});

🎭 Advanced Jest Features

🎯 Mocking with Type Safety

// πŸ“ services/userService.ts - Service to test
export interface User {
  id: string;
  name: string;
  email: string;
  isActive: boolean;
}

export interface UserRepository {
  findById(id: string): Promise<User | null>;
  create(user: Omit<User, 'id'>): Promise<User>;
  update(id: string, updates: Partial<User>): Promise<User>;
}

export class UserService {
  constructor(private userRepo: UserRepository) {}
  
  // πŸ‘€ Get active user by ID
  async getActiveUser(id: string): Promise<User | null> {
    const user = await this.userRepo.findById(id);
    return user?.isActive ? user : null;
  }
  
  // ✨ Create new user with validation
  async createUser(userData: Omit<User, 'id'>): Promise<User> {
    if (!userData.email.includes('@')) {
      throw new Error('Invalid email format πŸ“§');
    }
    
    return this.userRepo.create({
      ...userData,
      isActive: true // 🎯 New users are active by default
    });
  }
}
// πŸ“ __tests__/userService.test.ts - Mocking tests
import { UserService, type User, type UserRepository } from '../services/userService';

// 🎭 Create typed mock
const mockUserRepo: jest.Mocked<UserRepository> = {
  findById: jest.fn(),
  create: jest.fn(),
  update: jest.fn()
};

describe('UserService', () => {
  let userService: UserService;
  
  beforeEach(() => {
    userService = new UserService(mockUserRepo);
    // 🧹 Clear mocks between tests
    jest.clearAllMocks();
  });
  
  describe('getActiveUser', () => {
    it('should return active user', async () => {
      // 🎯 Arrange - setup mock data
      const mockUser: User = {
        id: '123',
        name: 'John Doe',
        email: '[email protected]',
        isActive: true
      };
      
      mockUserRepo.findById.mockResolvedValue(mockUser);
      
      // 🎬 Act
      const result = await userService.getActiveUser('123');
      
      // βœ… Assert
      expect(result).toEqual(mockUser);
      expect(mockUserRepo.findById).toHaveBeenCalledWith('123');
    });
    
    it('should return null for inactive user', async () => {
      const inactiveUser: User = {
        id: '123',
        name: 'John Doe',
        email: '[email protected]',
        isActive: false
      };
      
      mockUserRepo.findById.mockResolvedValue(inactiveUser);
      
      const result = await userService.getActiveUser('123');
      
      expect(result).toBeNull();
    });
    
    it('should return null when user not found', async () => {
      mockUserRepo.findById.mockResolvedValue(null);
      
      const result = await userService.getActiveUser('999');
      
      expect(result).toBeNull();
    });
  });
  
  describe('createUser', () => {
    it('should create user successfully', async () => {
      const userData = {
        name: 'Jane Smith',
        email: '[email protected]',
        isActive: false
      };
      
      const createdUser: User = {
        id: '456',
        ...userData,
        isActive: true // 🎯 Service sets to true
      };
      
      mockUserRepo.create.mockResolvedValue(createdUser);
      
      const result = await userService.createUser(userData);
      
      expect(result).toEqual(createdUser);
      expect(mockUserRepo.create).toHaveBeenCalledWith({
        ...userData,
        isActive: true
      });
    });
    
    it('should throw error for invalid email', async () => {
      const invalidUserData = {
        name: 'Invalid User',
        email: 'invalid-email', // 🚫 Missing @
        isActive: true
      };
      
      await expect(userService.createUser(invalidUserData))
        .rejects
        .toThrow('Invalid email format πŸ“§');
      
      // 🎯 Should not call repository
      expect(mockUserRepo.create).not.toHaveBeenCalled();
    });
  });
});

πŸ“Š Test Coverage and Reporting

🎯 Package.json Scripts

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --coverage --watchAll=false",
    "test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand"
  }
}

πŸ“ˆ Coverage Configuration

// πŸ“ jest.config.js - Advanced coverage setup
module.exports = {
  // ... other config
  
  // πŸ“Š Detailed coverage collection
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.{ts,tsx}',
    '!src/**/__tests__/**',
    '!src/**/__mocks__/**',
    '!src/index.ts'
  ],
  
  // 🎯 Coverage thresholds per path
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    },
    './src/services/': {
      branches: 90,
      functions: 90,
      lines: 90,
      statements: 90
    },
    './src/utils/': {
      branches: 85,
      functions: 85,
      lines: 85,
      statements: 85
    }
  },
  
  // πŸ“‹ Multiple coverage reporters
  coverageReporters: [
    'text',           // πŸ“Ί Console output
    'text-summary',   // πŸ“ Brief summary
    'lcov',          // πŸ”— For external tools
    'html',          // 🌐 HTML report
    'json'           // πŸ“„ JSON data
  ],
  
  // πŸ“ Coverage output directory
  coverageDirectory: 'coverage'
};

πŸš€ Testing Best Practices

βœ… Do’s and Don’ts

// βœ… GOOD: Descriptive test names
describe('Calculator add method', () => {
  it('should return correct sum for positive integers', () => {
    // Test implementation
  });
  
  it('should handle negative numbers correctly', () => {
    // Test implementation
  });
});

// ❌ BAD: Vague test names
describe('Calculator', () => {
  it('works', () => {
    // What does "works" mean?
  });
});

// βœ… GOOD: Arrange-Act-Assert pattern
it('should calculate compound interest correctly', () => {
  // 🎯 Arrange
  const principal = 1000;
  const rate = 0.05;
  const time = 2;
  
  // 🎬 Act
  const result = calculateCompoundInterest(principal, rate, time);
  
  // βœ… Assert
  expect(result).toBeCloseTo(1102.50, 2);
});

// βœ… GOOD: Type-safe test data
interface TestUser {
  id: string;
  name: string;
  email: string;
}

const createTestUser = (overrides: Partial<TestUser> = {}): TestUser => {
  return {
    id: '123',
    name: 'Test User',
    email: '[email protected]',
    ...overrides
  };
};

// βœ… GOOD: Testing error conditions
it('should throw error for division by zero', () => {
  expect(() => divide(10, 0)).toThrow('Division by zero');
});

// βœ… GOOD: Async testing
it('should fetch user data successfully', async () => {
  const userData = await fetchUser('123');
  
  expect(userData).toMatchObject({
    id: '123',
    name: expect.any(String),
    email: expect.stringContaining('@')
  });
});

🎯 Practical Exercise: E-Commerce Testing

// πŸ“ models/product.ts - Product management system
export interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
  category: string;
  isActive: boolean;
}

export interface CartItem {
  productId: string;
  quantity: number;
  priceAtTime: number;
}

export class ShoppingCart {
  private items: Map<string, CartItem> = new Map();
  
  // πŸ›’ Add item to cart
  addItem(product: Product, quantity: number): void {
    if (!product.isActive) {
      throw new Error(`Product ${product.name} is not available 🚫`);
    }
    
    if (quantity > product.stock) {
      throw new Error(`Not enough stock. Available: ${product.stock} πŸ“¦`);
    }
    
    const existingItem = this.items.get(product.id);
    
    if (existingItem) {
      const newQuantity = existingItem.quantity + quantity;
      if (newQuantity > product.stock) {
        throw new Error(`Total quantity exceeds stock πŸ“¦`);
      }
      
      this.items.set(product.id, {
        ...existingItem,
        quantity: newQuantity
      });
    } else {
      this.items.set(product.id, {
        productId: product.id,
        quantity,
        priceAtTime: product.price
      });
    }
  }
  
  // πŸ—‘οΈ Remove item from cart
  removeItem(productId: string): boolean {
    return this.items.delete(productId);
  }
  
  // πŸ”„ Update item quantity
  updateQuantity(productId: string, quantity: number): void {
    if (quantity <= 0) {
      this.removeItem(productId);
      return;
    }
    
    const item = this.items.get(productId);
    if (item) {
      this.items.set(productId, { ...item, quantity });
    }
  }
  
  // πŸ’° Calculate total
  getTotal(): number {
    let total = 0;
    for (const item of this.items.values()) {
      total += item.priceAtTime * item.quantity;
    }
    return Math.round(total * 100) / 100; // 🎯 Round to 2 decimals
  }
  
  // πŸ“‹ Get all items
  getItems(): CartItem[] {
    return Array.from(this.items.values());
  }
  
  // 🧹 Clear cart
  clear(): void {
    this.items.clear();
  }
  
  // πŸ“Š Get item count
  getItemCount(): number {
    let count = 0;
    for (const item of this.items.values()) {
      count += item.quantity;
    }
    return count;
  }
}
// πŸ“ __tests__/shoppingCart.test.ts - Complete test suite
import { ShoppingCart, type Product, type CartItem } from '../models/product';

describe('ShoppingCart', () => {
  let cart: ShoppingCart;
  let mockProduct: Product;
  
  beforeEach(() => {
    cart = new ShoppingCart();
    mockProduct = {
      id: 'laptop-001',
      name: 'Gaming Laptop',
      price: 1299.99,
      stock: 5,
      category: 'Electronics',
      isActive: true
    };
  });
  
  describe('addItem', () => {
    it('should add new item to cart', () => {
      cart.addItem(mockProduct, 2);
      
      const items = cart.getItems();
      expect(items).toHaveLength(1);
      expect(items[0]).toMatchObject({
        productId: 'laptop-001',
        quantity: 2,
        priceAtTime: 1299.99
      });
    });
    
    it('should increase quantity for existing item', () => {
      cart.addItem(mockProduct, 1);
      cart.addItem(mockProduct, 2);
      
      const items = cart.getItems();
      expect(items).toHaveLength(1);
      expect(items[0].quantity).toBe(3);
    });
    
    it('should throw error for inactive product', () => {
      const inactiveProduct = { ...mockProduct, isActive: false };
      
      expect(() => cart.addItem(inactiveProduct, 1))
        .toThrow('Product Gaming Laptop is not available 🚫');
    });
    
    it('should throw error when quantity exceeds stock', () => {
      expect(() => cart.addItem(mockProduct, 10))
        .toThrow('Not enough stock. Available: 5 πŸ“¦');
    });
    
    it('should throw error when total quantity exceeds stock', () => {
      cart.addItem(mockProduct, 3);
      
      expect(() => cart.addItem(mockProduct, 3))
        .toThrow('Total quantity exceeds stock πŸ“¦');
    });
  });
  
  describe('removeItem', () => {
    it('should remove item from cart', () => {
      cart.addItem(mockProduct, 2);
      
      const removed = cart.removeItem('laptop-001');
      
      expect(removed).toBe(true);
      expect(cart.getItems()).toHaveLength(0);
    });
    
    it('should return false for non-existent item', () => {
      const removed = cart.removeItem('non-existent');
      
      expect(removed).toBe(false);
    });
  });
  
  describe('updateQuantity', () => {
    beforeEach(() => {
      cart.addItem(mockProduct, 2);
    });
    
    it('should update item quantity', () => {
      cart.updateQuantity('laptop-001', 4);
      
      const items = cart.getItems();
      expect(items[0].quantity).toBe(4);
    });
    
    it('should remove item when quantity is zero', () => {
      cart.updateQuantity('laptop-001', 0);
      
      expect(cart.getItems()).toHaveLength(0);
    });
    
    it('should remove item when quantity is negative', () => {
      cart.updateQuantity('laptop-001', -1);
      
      expect(cart.getItems()).toHaveLength(0);
    });
  });
  
  describe('getTotal', () => {
    it('should calculate correct total for single item', () => {
      cart.addItem(mockProduct, 2);
      
      expect(cart.getTotal()).toBe(2599.98);
    });
    
    it('should calculate correct total for multiple items', () => {
      const mouseProduct: Product = {
        id: 'mouse-001',
        name: 'Gaming Mouse',
        price: 79.99,
        stock: 10,
        category: 'Electronics',
        isActive: true
      };
      
      cart.addItem(mockProduct, 1);
      cart.addItem(mouseProduct, 2);
      
      expect(cart.getTotal()).toBe(1459.97);
    });
    
    it('should return 0 for empty cart', () => {
      expect(cart.getTotal()).toBe(0);
    });
  });
  
  describe('getItemCount', () => {
    it('should return correct item count', () => {
      cart.addItem(mockProduct, 2);
      
      expect(cart.getItemCount()).toBe(2);
    });
    
    it('should return 0 for empty cart', () => {
      expect(cart.getItemCount()).toBe(0);
    });
  });
  
  describe('clear', () => {
    it('should clear all items from cart', () => {
      cart.addItem(mockProduct, 2);
      
      cart.clear();
      
      expect(cart.getItems()).toHaveLength(0);
      expect(cart.getTotal()).toBe(0);
    });
  });
});

🏁 Conclusion

Congratulations! πŸŽ‰ You’ve mastered the art of testing TypeScript applications with Jest. You now have the skills to:

  • βœ… Configure Jest for seamless TypeScript integration
  • πŸ§ͺ Write comprehensive tests with complete type safety
  • 🎭 Create sophisticated mocks that maintain type checking
  • πŸ“Š Measure code coverage and enforce quality standards
  • πŸ—οΈ Build professional testing workflows that scale with your team

You’ve learned to test everything from simple utilities to complex business logic, ensuring your TypeScript applications are bulletproof and maintainable.

Keep practicing these patterns, and you’ll become the testing champion your team needs! πŸ† Happy testing! πŸš€

πŸ”— Next Steps

Ready to level up further? Check out these advanced topics:

  • πŸ”„ Integration Testing with Supertest and databases
  • 🎭 End-to-End Testing with Cypress and Playwright
  • πŸ“Š Performance Testing and benchmarking
  • πŸ€– Test Automation in CI/CD pipelines
  • πŸ§ͺ Test-Driven Development (TDD) practices