为了账号安全,请及时绑定邮箱和手机立即绑定

Jest项目实战:新手入门指南

概述

本文将详细介绍如何在项目中使用Jest进行测试,包括Jest的安装、配置以及编写测试用例的方法,帮助新手快速掌握Jest项目实战。

Jest项目实战:新手入门指南
Jest简介与安装

什么是Jest

Jest是Facebook开源的一个JavaScript测试框架,主要用于测试JavaScript、TypeScript、React等Web应用。Jest具有零配置、快速运行、内置Mock函数等功能,使得编写和执行单元测试变得更加简单。Jest包含以下特性:

  • 自动收集测试
  • 断言库
  • 内置Mock函数
  • 代码覆盖率报告
  • 模拟DOM和浏览器对象
  • 测试环境快照
  • 自动创建测试文件
  • 命令行工具集成
  • Jest还内置了对React的特殊支持

安装Jest

Jest可以通过npm进行安装。在命令行中运行以下命令来安装Jest:

npm install --save-dev jest

安装完成后,Jest将作为项目的依赖项,在项目中使用其提供的各种测试工具。

配置Jest

默认情况下,Jest已经提供了基本配置。但如果需要自定义配置,可以创建一个jest.config.js文件,并在其中添加配置选项。例如,添加以下配置选项:

module.exports = {
  // 指定测试文件的扩展名
  testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|jsx|ts|tsx)$',
  // 指定模块解析的根目录
  moduleFileExtensions: [
    'js',
    'jsx',
    'ts',
    'tsx',
  ],
  // 指定测试文件的根目录
  testPathIgnorePatterns: ['\\\\node_modules\\\\'],
  // 指定是否使用ES模块
  moduleNameMapper: {
    '\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|svg|mp4|mov|mpeg)$': '<rootDir>/__mocks__/fileMock.js',
    '\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js',
  },
  // 指定测试时使用的端口号
  testEnvironment: 'jsdom',
};

这些配置选项可以根据项目需求进行调整,包括指定测试文件的扩展名、测试文件的根目录以及模块解析的根目录等。

编写第一个测试用例

测试函数

测试函数是测试中最基本的部分。编写测试函数需要选择一个断言库,并使用它提供的断言方法来验证函数的输出是否符合预期。例如:

// 被测函数
function add(a, b) {
  return a + b;
}

// 测试用例
describe('add', () => {
  it('should return the sum of two numbers', () => {
    expect(add(1, 2)).toBe(3);
    expect(add(-1, 1)).toBe(0);
  });
});

在示例代码中,describe函数用于定义测试套件,it函数用于定义具体的测试用例。expect函数用于断言实际结果是否符合预期结果。

测试异步函数

测试异步函数时,可以使用Promiseasync/await语法。Jest提供了async修饰符来简化异步测试的编写。例如:

// 被测异步函数
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 测试用例
describe('delay', () => {
  it('should resolve after the given delay', async () => {
    const start = Date.now();
    await delay(100);
    const end = Date.now();
    expect(end - start).toBeGreaterThan(100);
  });
});

在示例代码中,delay函数返回一个Promise,在it函数中使用await关键字等待Promise解析完成。通过比较开始时间和结束时间,确保delay函数运行了指定的时间。

测试类与方法

测试类和方法时,可以使用class来定义被测对象,并使用describeit来定义测试用例。可以通过实例化对象来调用类中的方法,并使用expect函数进行断言。例如:

// 被测类
class Calculator {
  add(a, b) {
    return a + b;
  }
  subtract(a, b) {
    return a - b;
  }
}

// 测试用例
describe('Calculator', () => {
  it('should add two numbers', () => {
    const calculator = new Calculator();
    expect(calculator.add(1, 2)).toBe(3);
  });

  it('should subtract two numbers', () => {
    const calculator = new Calculator();
    expect(calculator.subtract(2, 1)).toBe(1);
  });
});

在示例代码中,定义了一个Calculator类,并在describe函数中定义了测试用例。通过实例化Calculator类并调用其方法,验证结果是否符合预期。

Jest常用API详解

describe、it、expect

describe函数用于定义测试套件,通常包含一组相关的测试用例。it函数用于定义具体的测试用例,每个测试用例负责验证一个特定的功能。expect函数用于断言实际结果是否符合预期结果。例如:

describe('Calculator', () => {
  it('should add two numbers', () => {
    const calculator = new Calculator();
    expect(calculator.add(1, 2)).toBe(3);
  });

  it('should subtract two numbers', () => {
    const calculator = new Calculator();
    expect(calculator.subtract(2, 1)).toBe(1);
  });
});

在示例代码中,定义了一个Calculator类的测试套件,并使用describeit函数编写了两个测试用例。通过实例化Calculator类并调用其方法,使用expect断言验证结果是否符合预期。

Mock函数与Spy函数

Mock函数和Spy函数是Jest提供的用于模拟和监视函数行为的工具。Mock函数用于模拟函数的行为,Spy函数用于监视函数调用。例如:

// 被测函数
function fetchData(url) {
  return fetch(url);
}

// Mock函数
jest.mock('fetch', () => {
  return jest.fn().mockResolvedValue({ data: 'mocked data' });
});

// 测试用例
describe('fetchData', () => {
  it('should return mocked data', async () => {
    const result = await fetchData('https://api.example.com');
    expect(result.data).toBe('mocked data');
  });
});

在示例代码中,fetchData函数使用了fetch函数。通过jest.mock函数模拟了fetch函数的行为,并返回一个预设的Promise。在测试用例中,调用fetchData函数并验证返回的结果是否符合预期。

测试覆盖率

代码覆盖率是指测试代码对源代码的覆盖率。Jest提供了内置的代码覆盖率报告功能,可以通过配置选项启用代码覆盖率统计,并在测试运行时生成覆盖率报告。例如:

// 配置文件jest.config.js
module.exports = {
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coveragePathIgnorePatterns: ['\\\\node_modules\\\\'],
  testPathIgnorePatterns: ['\\\\node_modules\\\\'],
};

在示例代码中,通过配置选项collectCoverage启用代码覆盖率统计,并设置覆盖率报告的输出路径。coveragePathIgnorePatterns用于排除某些文件或目录的覆盖率统计。

Jest最佳实践

测试代码组织

测试代码组织的原则是将测试用例和代码分开,保持清晰的结构。通常将测试文件放在与代码文件相同的目录下,但放在__tests__子目录中。例如:

src/
  └── utils/
      ├── add.js
      └── __tests__
          └── add.test.js

在示例代码中,add.js是实际的代码文件,add.test.js是对应的测试文件。将测试文件放在__tests__子目录中,可以清晰地将测试文件和代码文件分开。

测试代码风格

测试代码风格的目标是保持一致性和可读性。建议遵循以下几点:

  • 使用describeit函数组织测试用例。
  • 使用expect函数进行断言。
  • 使用合适的描述语句,使测试用例的意图清晰明了。

示例代码:

describe('add', () => {
  it('should return the sum of two numbers', () => {
    expect(add(1, 2)).toBe(3);
  });

  it('should return zero when adding negative and positive numbers', () => {
    expect(add(-1, 1)).toBe(0);
  });
});

在示例代码中,使用了describeit函数组织测试用例,并使用了合适的描述语句,使测试用例的意图清晰明了。

依赖注入与Mock

依赖注入和Mock是提高测试代码可维护性和可读性的重要手段。通常在编写测试用例时,将依赖项作为参数传递给被测函数,而不是直接引入依赖项。例如:

// 描述类
class ServiceClient {
  fetch(url) {
    return fetch(url);
  }
}

// 被测函数
function fetchData(url, client) {
  return client.fetch(url);
}

// 测试用例
describe('fetchData', () => {
  it('should return mocked data', async () => {
    const mockFetch = jest.fn().mockResolvedValue({ data: 'mocked data' });
    const mockClient = new ServiceClient();
    mockClient.fetch = mockFetch;
    const result = await fetchData('https://api.example.com', mockClient);
    expect(result.data).toBe('mocked data');
    expect(mockFetch).toHaveBeenCalledWith('https://api.example.com');
  });
});

在示例代码中,fetchData函数接收一个fetch函数作为参数。在测试用例中,通过传递一个模拟的fetch函数来测试fetchData函数的行为。这种方式可以避免直接引入依赖项,使测试用例更加灵活和可维护。

Jest与CI/CD集成

在持续集成环境中运行Jest

在持续集成环境中运行Jest,可以确保每次代码提交后自动运行测试用例,确保代码质量。通常在持续集成服务(如GitHub Actions、GitLab CI等)中配置Jest的执行任务。例如:

name: Test
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Install dependencies
      run: npm ci
    - name: Run tests
      run: npm test

在示例代码中,配置了一个GitHub Actions的工作流,定义了test作业,运行在ubuntu-latest环境中。在作业中,执行了npm cinpm test命令,以安装依赖项和运行测试用例。

运行测试并收集覆盖率数据

在持续集成环境中运行Jest时,可以通过配置选项启用代码覆盖率统计,并收集覆盖率数据。例如:

// 配置文件jest.config.js
module.exports = {
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coveragePathIgnorePatterns: ['\\\\node_modules\\\\'],
  testPathIgnorePatterns: ['\\\\node_modules\\\\'],
};

在示例代码中,通过配置选项collectCoverage启用代码覆盖率统计,并设置覆盖率报告的输出路径。coveragePathIgnorePatterns用于排除某些文件或目录的覆盖率统计。

集成Jest的测试报告

集成Jest的测试报告可以帮助持续集成服务更好地展示测试结果。通常在持续集成服务中配置任务,将测试报告上传到指定的位置或服务。例如:

name: Test
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Install dependencies
      run: npm ci
    - name: Run tests
      run: npm test
    - name: Upload coverage report
      uses: coverallsapp/github-action@v1.6.5
      with:
        path_to_coverages: coverage

在示例代码中,定义了一个GitHub Actions的工作流,运行在ubuntu-latest环境中。在作业中,执行了npm cinpm test命令,以安装依赖项和运行测试用例。最后,使用coverallsapp/github-action任务将覆盖率报告上传到Coveralls服务。

常见问题与解决方案

测试覆盖率低的原因与解决方法

测试覆盖率低的原因可能包括:

  • 测试覆盖不全面,未覆盖所有代码分支和路径。
  • 测试用例简单,缺乏复杂性和边缘情况的测试。
  • 代码中存在未测试的部分,例如复杂的业务逻辑或异常处理。

解决方法包括:

  • 逐步增加测试覆盖,确保所有代码分支都被覆盖。
  • 编写更多的测试用例,包括复杂和边缘情况。
  • 重构代码,使其更容易测试。

示例代码:

// 被测函数
function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new Error('Both arguments must be numbers');
  }
  return a + b;
}

// 测试用例
describe('add', () => {
  it('should throw error when arguments are not numbers', () => {
    expect(() => add('1', 2)).toThrow('Both arguments must be numbers');
    expect(() => add(1, '2')).toThrow('Both arguments must be numbers');
  });

  it('should return the sum of two numbers', () => {
    expect(add(1, 2)).toBe(3);
    expect(add(-1, 1)).toBe(0);
  });
});

在示例代码中,定义了一个抛出错误的add函数。在测试用例中,编写了多个测试用例,包括抛出错误的情况和正常情况。

Mock函数的常见误区

Mock函数的常见误区包括:

  • 过度依赖Mock函数,导致代码难以理解和维护。
  • Mock函数的实现过于复杂,难以理解和调试。
  • Mock函数和真实函数的实现不一致,导致测试结果不一致。

解决方法包括:

  • 仅在必要时使用Mock函数,尽量使用真实的依赖项。
  • Mock函数的实现应尽可能简单,只模拟必要的行为。
  • Mock函数和真实函数的实现应保持一致,确保测试结果的一致性。

示例代码:

// 被测函数
function fetchData(url) {
  return fetch(url);
}

// Mock函数
jest.mock('fetch', () => {
  return jest.fn().mockResolvedValue({ data: 'mocked data' });
});

// 测试用例
describe('fetchData', () => {
  it('should return mocked data', async () => {
    const result = await fetchData('https://api.example.com');
    expect(result.data).toBe('mocked data');
    expect(fetch).toHaveBeenCalledWith('https://api.example.com');
  });
});

在示例代码中,定义了一个使用fetch函数的fetchData函数。在测试用例中,使用Mock函数模拟了fetch函数的行为,并验证了返回的结果和函数调用。

Jest与TypeScript配合使用

Jest与TypeScript配合使用时,需要注意以下几点:

  • TypeScript需要安装并配置,确保项目支持TypeScript。
  • Jest需要安装TypeScript相关的依赖项。
  • 配置文件需要包含TypeScript相关的选项。

示例代码:

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

在示例代码中,安装了Jest和ts-jest依赖项,并安装了@types/jest类型定义。

示例配置文件jest.config.js

module.exports = {
  preset: 'ts-jest',
  testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(ts|tsx|js|jsx)$',
  moduleFileExtensions: [
    'ts',
    'tsx',
    'js',
    'jsx',
    'json',
    'node',
  ],
};

在示例配置文件中,使用了ts-jest预设,并配置了测试文件的扩展名和模块解析的根目录。

示例代码:

// 被测函数
function add(a: number, b: number): number {
  return a + b;
}

// 测试用例
describe('add', () => {
  it('should return the sum of two numbers', () => {
    expect(add(1, 2)).toBe(3);
    expect(add(-1, 1)).toBe(0);
  });
});

在示例代码中,定义了一个返回数字类型结果的add函数。在测试用例中,使用expect断言验证函数的结果是否符合预期。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消