Skip to content

Jest Configuration

Complete guide to Jest testing configuration in the TypeScript Project Template.

Overview

The template uses Jest with TypeScript support, providing:

  • ES modules compatibility with ts-jest
  • TypeScript compilation for test files
  • Coverage reporting with configurable thresholds
  • Async testing patterns for modern JavaScript

Configuration File (jest.config.js)

Basic Structure

javascript
export default {
  preset: 'ts-jest/presets/default-esm',
  extensionsToTreatAsEsm: ['.ts'],
  testEnvironment: 'node',
  
  // TypeScript configuration
  transform: {
    '^.+\\.ts$': ['ts-jest', {
      useESM: true,
      tsconfig: {
        module: 'ESNext'
      }
    }]
  },
  
  // Module resolution
  moduleNameMapping: {
    '^(\\.{1,2}/.*)\\.js$': '$1'
  },
  
  // Coverage configuration
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
  
  // Coverage thresholds
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};

Key Configuration Options

TypeScript Integration

ES Modules Support

javascript
{
  preset: 'ts-jest/presets/default-esm',
  extensionsToTreatAsEsm: ['.ts'],
  transform: {
    '^.+\\.ts$': ['ts-jest', {
      useESM: true
    }]
  }
}

Module Name Mapping

javascript
{
  moduleNameMapping: {
    // Map .js imports to .ts files
    '^(\\.{1,2}/.*)\\.js$': '$1'
  }
}

Coverage Configuration

Coverage Thresholds

javascript
{
  coverageThreshold: {
    global: {
      branches: 80,    // 80% branch coverage
      functions: 80,   // 80% function coverage
      lines: 80,       // 80% line coverage
      statements: 80   // 80% statement coverage
    }
  }
}

Coverage Reporters

javascript
{
  coverageReporters: [
    'text',      // Terminal output
    'lcov',      // For CI/CD integration
    'html',      // Browser-viewable report
    'json'       // Machine-readable format
  ]
}

Test Environment

Node.js Environment

javascript
{
  testEnvironment: 'node',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
}

Running Tests

Basic Commands

bash
# Run all tests
npm test

# Watch mode (auto-rerun on changes)
npm run test:watch

# Generate coverage report
npm run test:coverage

# CI mode (no watch, single run)
npm run test:ci

Advanced Test Commands

bash
# Run specific test file
npm test -- index.test.ts

# Run tests matching pattern
npm test -- --testNamePattern="should format"

# Debug mode
npm test -- --runInBand --detectOpenHandles

# Update snapshots
npm test -- --updateSnapshot

Test File Structure

Naming Conventions

__tests__/
├── index.test.ts      # Tests for src/index.ts
├── utils.test.ts      # Tests for src/utils.ts
└── helpers/
    └── setup.test.ts  # Tests for src/helpers/setup.ts

Test File Template

typescript
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import { functionToTest } from '../src/module.js';

describe('functionToTest', () => {
  beforeEach(() => {
    // Setup before each test
  });

  afterEach(() => {
    // Cleanup after each test
  });

  it('should handle normal input', () => {
    const result = functionToTest('input');
    expect(result).toBe('expected');
  });

  it('should handle edge cases', () => {
    const result = functionToTest('');
    expect(result).toBe('default');
  });

  it('should throw on invalid input', () => {
    expect(() => functionToTest(null)).toThrow();
  });
});

Async Testing

Promise-Based Testing

typescript
import { delay } from '../src/utils.js';

describe('delay', () => {
  it('should resolve after specified time', async () => {
    const startTime = Date.now();
    await delay(100);
    const endTime = Date.now();
    
    expect(endTime - startTime).toBeGreaterThanOrEqual(90);
    expect(endTime - startTime).toBeLessThan(150);
  });

  it('should handle zero delay', async () => {
    await expect(delay(0)).resolves.toBeUndefined();
  });
});

Error Testing

typescript
describe('safeJsonParse', () => {
  it('should reject invalid JSON', () => {
    const result = safeJsonParse('invalid json');
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.error).toContain('Unexpected token');
    }
  });
});

Mocking

Function Mocking

typescript
import { jest } from '@jest/globals';

describe('with mocks', () => {
  const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {});

  afterEach(() => {
    mockConsoleLog.mockClear();
  });

  afterAll(() => {
    mockConsoleLog.mockRestore();
  });

  it('should log message', () => {
    functionThatLogs('test');
    expect(mockConsoleLog).toHaveBeenCalledWith('test');
  });
});

Module Mocking

typescript
// Mock entire module
jest.mock('../src/utils.js', () => ({
  delay: jest.fn().mockResolvedValue(undefined),
  formatTimestamp: jest.fn().mockReturnValue('mocked-timestamp')
}));

// Use mocked functions
import { delay } from '../src/utils.js';
const mockDelay = delay as jest.MockedFunction<typeof delay>;

Coverage Analysis

Coverage Reports

bash
# Generate coverage report
npm run test:coverage

# View HTML report
open coverage/lcov-report/index.html

# View text summary
cat coverage/lcov.info

Coverage Thresholds

javascript
{
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    },
    // Per-file thresholds
    './src/critical.ts': {
      branches: 95,
      functions: 95,
      lines: 95,
      statements: 95
    }
  }
}

Excluding Files from Coverage

javascript
{
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/test-utils/**'
  ],
  coveragePathIgnorePatterns: [
    '/node_modules/',
    '/dist/',
    '/coverage/'
  ]
}

Debugging Tests

VS Code Integration

json
{
  "type": "node",
  "request": "launch",
  "name": "Jest Tests",
  "program": "${workspaceFolder}/node_modules/.bin/jest",
  "args": ["--runInBand"],
  "console": "integratedTerminal",
  "internalConsoleOptions": "neverOpen"
}

Debug Specific Tests

bash
# Debug single test file
npm test -- --runInBand index.test.ts

# Debug with Node.js inspector
node --inspect-brk node_modules/.bin/jest --runInBand

# Verbose output
npm test -- --verbose

Advanced Configuration

Custom Test Environment

javascript
// jest.setup.js
import { jest } from '@jest/globals';

// Global test utilities
global.testUtils = {
  wait: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
  generateTestData: (count) => Array.from({ length: count }, (_, i) => ({ id: i }))
};

// Mock global objects
global.fetch = jest.fn();

Performance Optimization

javascript
{
  // Parallel execution
  maxWorkers: '50%',
  
  // Cache configuration
  cache: true,
  cacheDirectory: '<rootDir>/.jest-cache',
  
  // Faster test discovery
  testPathIgnorePatterns: [
    '/node_modules/',
    '/dist/',
    '/coverage/'
  ]
}

Custom Matchers

javascript
// jest.setup.js
expect.extend({
  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling;
    if (pass) {
      return {
        message: () => `expected ${received} not to be within range ${floor} - ${ceiling}`,
        pass: true,
      };
    } else {
      return {
        message: () => `expected ${received} to be within range ${floor} - ${ceiling}`,
        pass: false,
      };
    }
  },
});

Troubleshooting

Common Issues

ES Modules Errors

bash
# Error: Cannot use import statement outside a module
# Solution: Ensure proper ES modules configuration
{
  "preset": "ts-jest/presets/default-esm",
  "extensionsToTreatAsEsm": [".ts"]
}

TypeScript Compilation Errors

bash
# Error: TypeScript compilation failed
# Solution: Check tsconfig.json and Jest transform configuration
npm run build  # Verify TypeScript compiles

Coverage Threshold Failures

bash
# Error: Coverage threshold not met
# Solution: Add more tests or adjust thresholds
npm run test:coverage  # See detailed coverage report

Performance Issues

javascript
{
  // Reduce parallel workers
  maxWorkers: 1,
  
  // Disable coverage for faster runs
  collectCoverage: false,
  
  // Use faster transform
  transform: {
    '^.+\\.ts$': 'ts-jest'
  }
}

Best Practices

Test Organization

  • Mirror source structure in test files
  • Group related tests with describe blocks
  • Use descriptive test names that explain the scenario
  • Keep tests focused on single behaviors

Test Quality

  • Test expected behavior first
  • Include edge cases and error conditions
  • Use appropriate assertions for the scenario
  • Mock external dependencies appropriately

Maintenance

  • Regular coverage review to identify gaps
  • Update tests when code changes
  • Refactor test utilities to reduce duplication
  • Monitor test performance and optimize as needed

Released under the MIT License.