jest-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Jest Testing Skill

Jest测试技能

Master testing Node.js applications with Jest - the delightful JavaScript testing framework.
掌握使用Jest测试Node.js应用的方法——Jest是一款便捷的JavaScript测试框架。

Quick Start

快速入门

Test in 3 steps:
  1. Install -
    npm install --save-dev jest supertest
  2. Write Test - Create
    *.test.js
    files
  3. Run -
    npm test
3步完成测试:
  1. 安装 -
    npm install --save-dev jest supertest
  2. 编写测试用例 - 创建
    *.test.js
    文件
  3. 运行测试 -
    npm test

Core Concepts

核心概念

Basic Test Structure

基础测试结构

javascript
// sum.test.js
const sum = require('./sum');

describe('sum function', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });

  test('adds negative numbers', () => {
    expect(sum(-1, -2)).toBe(-3);
  });
});
javascript
// sum.test.js
const sum = require('./sum');

describe('sum function', () => {
  test('adds 1 + 2 to equal 3', () => {
    expect(sum(1, 2)).toBe(3);
  });

  test('adds negative numbers', () => {
    expect(sum(-1, -2)).toBe(-3);
  });
});

Jest Configuration

Jest配置

javascript
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "jest": {
    "testEnvironment": "node",
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80
      }
    }
  }
}
javascript
// package.json
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  },
  "jest": {
    "testEnvironment": "node",
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80
      }
    }
  }
}

Unit Testing

单元测试

javascript
// userService.test.js
const UserService = require('./userService');
const User = require('./models/User');

jest.mock('./models/User');

describe('UserService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('createUser', () => {
    it('should create user successfully', async () => {
      const userData = {
        name: 'John',
        email: 'john@example.com'
      };

      User.create.mockResolvedValue({ id: 1, ...userData });

      const result = await UserService.createUser(userData);

      expect(User.create).toHaveBeenCalledWith(userData);
      expect(result.id).toBe(1);
    });

    it('should throw error for duplicate email', async () => {
      User.create.mockRejectedValue(new Error('Email exists'));

      await expect(UserService.createUser({}))
        .rejects
        .toThrow('Email exists');
    });
  });
});
javascript
// userService.test.js
const UserService = require('./userService');
const User = require('./models/User');

jest.mock('./models/User');

describe('UserService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('createUser', () => {
    it('should create user successfully', async () => {
      const userData = {
        name: 'John',
        email: 'john@example.com'
      };

      User.create.mockResolvedValue({ id: 1, ...userData });

      const result = await UserService.createUser(userData);

      expect(User.create).toHaveBeenCalledWith(userData);
      expect(result.id).toBe(1);
    });

    it('should throw error for duplicate email', async () => {
      User.create.mockRejectedValue(new Error('Email exists'));

      await expect(UserService.createUser({}))
        .rejects
        .toThrow('Email exists');
    });
  });
});

Learning Path

学习路径

Beginner (1-2 weeks)

入门阶段(1-2周)

  • ✅ Setup Jest and write basic tests
  • ✅ Understand test structure (describe/it/expect)
  • ✅ Learn matchers (toBe, toEqual, etc.)
  • ✅ Test synchronous functions
  • ✅ 搭建Jest环境并编写基础测试用例
  • ✅ 理解测试结构(describe/it/expect)
  • ✅ 学习匹配器(toBe, toEqual等)
  • ✅ 测试同步函数

Intermediate (3-4 weeks)

进阶阶段(3-4周)

  • ✅ Test async functions
  • ✅ Mock modules and functions
  • ✅ API testing with Supertest
  • ✅ Code coverage reports
  • ✅ 测试异步函数
  • ✅ Mock模块和函数
  • ✅ 使用Supertest进行API测试
  • ✅ 生成代码覆盖率报告

Advanced (5-6 weeks)

高级阶段(5-6周)

  • ✅ Integration testing
  • ✅ Test database operations
  • ✅ CI/CD integration
  • ✅ Performance testing
  • ✅ 集成测试
  • ✅ 测试数据库操作
  • ✅ CI/CD集成
  • ✅ 性能测试

API Testing with Supertest

使用Supertest进行API测试

javascript
const request = require('supertest');
const app = require('./app');

describe('User API', () => {
  describe('POST /api/users', () => {
    it('should create new user', async () => {
      const userData = {
        name: 'John',
        email: 'john@example.com',
        password: 'password123'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect('Content-Type', /json/)
        .expect(201);

      expect(response.body).toHaveProperty('id');
      expect(response.body.email).toBe(userData.email);
    });

    it('should return 400 for invalid email', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({ email: 'invalid' })
        .expect(400);

      expect(response.body).toHaveProperty('error');
    });
  });

  describe('GET /api/users/:id', () => {
    it('should return user by id', async () => {
      const response = await request(app)
        .get('/api/users/123')
        .expect(200);

      expect(response.body.id).toBe('123');
    });

    it('should return 404 for non-existent user', async () => {
      await request(app)
        .get('/api/users/999')
        .expect(404);
    });
  });
});
javascript
const request = require('supertest');
const app = require('./app');

describe('User API', () => {
  describe('POST /api/users', () => {
    it('should create new user', async () => {
      const userData = {
        name: 'John',
        email: 'john@example.com',
        password: 'password123'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect('Content-Type', /json/)
        .expect(201);

      expect(response.body).toHaveProperty('id');
      expect(response.body.email).toBe(userData.email);
    });

    it('should return 400 for invalid email', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({ email: 'invalid' })
        .expect(400);

      expect(response.body).toHaveProperty('error');
    });
  });

  describe('GET /api/users/:id', () => {
    it('should return user by id', async () => {
      const response = await request(app)
        .get('/api/users/123')
        .expect(200);

      expect(response.body.id).toBe('123');
    });

    it('should return 404 for non-existent user', async () => {
      await request(app)
        .get('/api/users/999')
        .expect(404);
    });
  });
});

Mocking Patterns

Mock模式

javascript
// Mock entire module
jest.mock('axios');
const axios = require('axios');

test('fetches data from API', async () => {
  axios.get.mockResolvedValue({ data: { id: 1 } });

  const result = await fetchUser(1);

  expect(axios.get).toHaveBeenCalledWith('/api/users/1');
  expect(result.id).toBe(1);
});

// Spy on function
test('calls callback', () => {
  const callback = jest.fn();

  processData('test', callback);

  expect(callback).toHaveBeenCalledWith('test');
  expect(callback).toHaveBeenCalledTimes(1);
});

// Mock timers
jest.useFakeTimers();

test('delays execution', () => {
  const callback = jest.fn();

  setTimeout(callback, 1000);
  jest.advanceTimersByTime(1000);

  expect(callback).toHaveBeenCalled();
});
javascript
// Mock整个模块
jest.mock('axios');
const axios = require('axios');

test('fetches data from API', async () => {
  axios.get.mockResolvedValue({ data: { id: 1 } });

  const result = await fetchUser(1);

  expect(axios.get).toHaveBeenCalledWith('/api/users/1');
  expect(result.id).toBe(1);
});

// 监听函数
test('calls callback', () => {
  const callback = jest.fn();

  processData('test', callback);

  expect(callback).toHaveBeenCalledWith('test');
  expect(callback).toHaveBeenCalledTimes(1);
});

// Mock定时器
jest.useFakeTimers();

test('delays execution', () => {
  const callback = jest.fn();

  setTimeout(callback, 1000);
  jest.advanceTimersByTime(1000);

  expect(callback).toHaveBeenCalled();
});

Test Lifecycle Hooks

测试生命周期钩子

javascript
describe('User Tests', () => {
  beforeAll(async () => {
    // Setup test database
    await connectTestDB();
  });

  afterAll(async () => {
    // Cleanup
    await disconnectTestDB();
  });

  beforeEach(async () => {
    // Clear data before each test
    await User.deleteMany({});
  });

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

  test('...', () => {});
});
javascript
describe('User Tests', () => {
  beforeAll(async () => {
    // 搭建测试数据库
    await connectTestDB();
  });

  afterAll(async () => {
    // 清理资源
    await disconnectTestDB();
  });

  beforeEach(async () => {
    // 每次测试前清理数据
    await User.deleteMany({});
  });

  afterEach(() => {
    // 每次测试后清理
    jest.clearAllMocks();
  });

  test('...', () => {});
});

Jest Matchers

Jest匹配器

javascript
// Equality
expect(value).toBe(expected)        // Strict equality (===)
expect(value).toEqual(expected)     // Deep equality
expect(value).not.toBe(expected)    // Negation

// Truthiness
expect(value).toBeDefined()
expect(value).toBeNull()
expect(value).toBeTruthy()
expect(value).toBeFalsy()

// Numbers
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3)
expect(value).toBeLessThan(5)
expect(value).toBeCloseTo(0.3)      // Floating point

// Strings
expect(string).toMatch(/pattern/)
expect(string).toContain('substring')

// Arrays
expect(array).toContain(item)
expect(array).toHaveLength(3)

// Objects
expect(obj).toHaveProperty('key')
expect(obj).toMatchObject({ key: 'value' })

// Exceptions
expect(() => fn()).toThrow()
expect(() => fn()).toThrow('error message')

// Async
await expect(promise).resolves.toBe(value)
await expect(promise).rejects.toThrow()
javascript
// 相等性
expect(value).toBe(expected)        // 严格相等(===)
expect(value).toEqual(expected)     // 深度相等
expect(value).not.toBe(expected)    // 取反

// 真值判断
expect(value).toBeDefined()
expect(value).toBeNull()
expect(value).toBeTruthy()
expect(value).toBeFalsy()

// 数字
expect(value).toBeGreaterThan(3)
expect(value).toBeGreaterThanOrEqual(3)
expect(value).toBeLessThan(5)
expect(value).toBeCloseTo(0.3)      // 浮点数比较

// 字符串
expect(string).toMatch(/pattern/)
expect(string).toContain('substring')

// 数组
expect(array).toContain(item)
expect(array).toHaveLength(3)

// 对象
expect(obj).toHaveProperty('key')
expect(obj).toMatchObject({ key: 'value' })

// 异常
expect(() => fn()).toThrow()
expect(() => fn()).toThrow('error message')

// 异步操作
await expect(promise).resolves.toBe(value)
await expect(promise).rejects.toThrow()

Code Coverage

代码覆盖率

bash
undefined
bash
undefined

Run with coverage

带覆盖率报告运行测试

npm test -- --coverage
npm test -- --coverage

Coverage report shows:

覆盖率报告包含:

- Statements: % of code executed

- Statements: 已执行代码的百分比

- Branches: % of if/else paths

- Branches: if/else分支的执行百分比

- Functions: % of functions called

- Functions: 已调用函数的百分比

- Lines: % of lines executed

- Lines: 已执行代码行的百分比

undefined
undefined

Testing Best Practices

测试最佳实践

  • ✅ AAA pattern: Arrange, Act, Assert
  • ✅ One assertion per test (ideally)
  • ✅ Descriptive test names
  • ✅ Test edge cases
  • ✅ Mock external dependencies
  • ✅ Clean up after tests
  • ✅ Avoid test interdependence
  • ✅ Aim for 80%+ coverage
  • ✅ AAA模式:准备(Arrange)、执行(Act)、断言(Assert)
  • ✅ 每个测试用例尽量只包含一个断言
  • ✅ 测试用例名称具有描述性
  • ✅ 测试边界情况
  • ✅ Mock外部依赖
  • ✅ 测试后清理资源
  • ✅ 避免测试用例之间的依赖
  • ✅ 目标覆盖率达到80%以上

CI/CD Integration

CI/CD集成

yaml
undefined
yaml
undefined

.github/workflows/test.yml

.github/workflows/test.yml

name: Tests
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v3
  - uses: actions/setup-node@v3
    with:
      node-version: 18
  - run: npm ci
  - run: npm test -- --coverage
  - uses: codecov/codecov-action@v3
undefined
name: Tests
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v3
  - uses: actions/setup-node@v3
    with:
      node-version: 18
  - run: npm ci
  - run: npm test -- --coverage
  - uses: codecov/codecov-action@v3
undefined

When to Use

适用场景

Use Jest testing when:
  • Building Node.js applications
  • Need comprehensive test coverage
  • Want fast, parallel test execution
  • Require mocking and snapshot testing
  • Implementing CI/CD pipelines
在以下场景使用Jest测试:
  • 开发Node.js应用
  • 需要全面的测试覆盖率
  • 希望快速、并行执行测试
  • 需要Mock和快照测试
  • 实现CI/CD流水线

Related Skills

相关技能

  • Express REST API (test API endpoints)
  • Async Programming (test async code)
  • Database Integration (test DB operations)
  • JWT Authentication (test auth flows)
  • Express REST API(测试API端点)
  • 异步编程(测试异步代码)
  • 数据库集成(测试数据库操作)
  • JWT认证(测试认证流程)

Resources

参考资源