Jest单元测试框架

重要通知

Jest 支持 Babel、TypeScript、Node、React、Angular、Vue 等诸多框架!

基本概况

Jest 是一个来自于facebook出品的通用JavaScript 测试框架。

论单元测试的重要性

  • 如何度量团队中每个人的代码质量?

  • 如何保障团队中每个人的代码质量?

  • 如何保证review代码的100%完整性与正确性?

  • 敢随时轻易地重构旧有的业务代码吗?

  • 在没有测试人员的情况下,敢随时发布上线业务代码吗?

  • 函数、模块、类和组件

  • 编写单元测试是为了验证小型、独立的代码单元是否按预期工作。单元测试通常涵盖单个函数、类、可组合项或模块。

__test__

index.test.ts
index.spec.ts

命名规范

只要满足规范一或规范二即可。

  • 规范一: 与业务文件独立开来,新建文件夹__test__,单独存放测试用例
  • 规范二: 与业务文件放在一起,例如业务文件为index.ts,则测试用例文件为index.test.ts或index.spec.ts
# 文件夹
__tests__


# 文件名称
.test.ts .test.js
.spec.ts .spec.js

安装配置

> pnpm install --save-dev jest
> jest --init
> pnpm run test
module.exports = {
  // 指定 rootDir 对应的路径,如果与 jest.config.js 文件所在的目录相同,则不用设置
  rootDir: './',
  // 使用 ts-jest 作为预处理器
  // 参考:https://kulshekhar.github.io/ts-jest/docs/getting-started/presets
  preset: 'ts-jest/presets/js-with-ts',
  // 是否报告每个test的执行详情
  verbose: true,
  // 覆盖率结果输出的文件夹
  coverageDirectory: '__tests__/coverage',
  // 模块后缀名
  moduleFileExtensions: ['js', 'json', 'ts', 'jsx', 'tsx'],
  // 初始化配置文件路径
  setupFiles: [
    // 注意,如果不需要接入 enzyme 则不用引用此初始化文件
    '<rootDir>/__tests__/setup/jest.setup.ts',
    '<rootDir>/__tests__/setup/mock.setup.ts',
  ],

  // 测试运行环境,jsdom类浏览器环境
  testEnvironment: 'jsdom',

  // 匹配测试文件
  testMatch: [
    '<rootDir>/editor-components/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
    '<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
    '<rootDir>/__tests__/*.{js,jsx,ts,tsx}',
  ],
  // 忽略测试文件
  testPathIgnorePatterns: ['/node_modules/'],
  // 模块别名,注意要根据项目实际的 tsconfig.json 配置更新
  moduleNameMapper: {
    // 注意,如果不需要接入 enzyme 则不用设置对 css 文件的模拟导入
    // 代理css文件的导入
    '\\.(c|le)ss$': 'identity-obj-proxy',
    // 简单模拟静态资源的导入,因为已经使用了下面的 transform 字段配置处理方式,所以此处注释掉
    // '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
    //   '<rootDir>/__tests__/mock/file/index.js',
    // 识别模块相对地址
    '^(api|utils|actions|components|helpers|reducers)/(.*)$': '<rootDir>/src/$1/$2',
    // 识别全局测试文件
    '^__tests__/(.*)$': '<rootDir>/__tests__/$1',
  },

  // 因为要模拟实际的静态资源载入情况,所以指定了特定文件的处理器
  transform: {
    // 代理静态资源的导入
    // 已经指定了预处理器为 ts-jest, 所以下面不用再手动指定 js 文件的处理器
    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
      '<rootDir>/__tests__/mock/file/transformer.js',
    // 此种静态资源模拟导入方式,就需要显示的配置 babel-jest 为 js 文件的处理器
    // 参考:https://jestjs.io/zh-Hans/docs/webpack#模拟-css-模块
    // '\\.[jt]sx?$': ['babel-jest', { rootMode: 'upward' }],

    "^.+\.vue$": "@vue/vue3-jest",
    "^.+\js$": "babel-jest",
    "^.+\\.ts$": "ts-jest"
  },

  // 编译时不忽略node_modules/@fe文件夹
  transformIgnorePatterns: ['/node_modules/(?!@fe)'],
};

代码示例

  • a.js
function sum(a, b) {
  return a + b;
}
module.exports = sum;
  • a.test.js
import { describe, expect, test, it } from '@jest/globals';
const jestA = require('./a');


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

属性与API接口

匹配器(Matcher)

import { describe, expect, test, it } from '@jest/globals';


test('描述语句', () => {
  expect(...).toBe(...);     // 等于 | Object.is()
  expect(...).toEqual(...);  // 对比 | 

  expect(...).toBeNull();
  expect(...).toBeDefined();
  expect(...).not.toBeUndefined();
  expect(...).not.toBeTruthy();
  expect(...).toBeFalsy();

  // 非
  expect(...).not.toBeNull();

  // Numbers 数字
  expect(...).toBeGreaterThan(...);
  expect(...).toBeGreaterThanOrEqual(...);
  expect(...).toBeLessThan(...);
  expect(...).toBeLessThanOrEqual(...);

  // Strings 字符串
  expect(...).not.toMatch([RegExp]);

  // Arrays and iterables 数组和可迭代对象
  expect(...).toContain(...);

  // Exceptions
  expect(...).toThrow(...);




  // 对于比较浮点数相等,使用 而不是 ,因为你不希望测试取决于一个小小的舍入误差。
  test('两个浮点数字相加', () => {
    const value = 0.1 + 0.2;
    //expect(value).toBe(0.3);     这句会报错,因为浮点数有舍入误差
    expect(value).toBeCloseTo(0.3); // 这句可以运行
  });
});  

修饰符

test('the best flavor is not coconut', () => {
  expect(bestLaCroixFlavor()).not.toBe('coconut');
});

快照测试

当你想要确保你的UI不会有意外的改变,快照测试是非常有用的工具。典型的做法是在渲染了UI组件之后,保存一个快照文件,检测他是否与保存在单元测试旁的快照文件相匹配。 若两个快照不匹配,测试将失败:有可能做了意外的更改,或者UI组件已经更新到了新版本。

  • 第一次运行此测试时,Jest 会创建一个快照文件。
  • 在当前测用例文件同级目录创建一个__snapshots__目录,并在该目录中创建xxx.test.ts.snap的文件
import { describe, expect, test, it } from '@jest/globals';
import renderer from 'react-test-renderer';
import Link from '../Link';


it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.facebook.com">Facebook</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

更新快照

--testNamePattern限制仅仅需要生成的一部分快照文件

> jest --updateSnapshot [--testNamePattern] # 全写
> jest -u [--testNamePattern] # 简写

> npm run test:unit -- -u

属性匹配器

项目中常常会有不定值字段生成(例如IDs和Dates)。如果你试图对这些对象进行快照测试,每个测试都会失败。针对这些情况,Jest允许为任何属性提供匹配器(非对称匹配器)。在快照写入或者测试前只检查这些匹配器是否通过,而不是具体的值。

it('will check the matchers and pass', () => {
  const user = {
    createdAt: new Date(),
    id: Math.floor(Math.random() * 20),
    name: 'LeBron James',
  };

  expect(user).toMatchSnapshot({
    createdAt: expect.any(Date),
    id: expect.any(Number),
  });
});

测试异步代码

import { describe, expect, test, it } from '@jest/globals';


test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
});

Vue3与vite集成Jest

# yarn
> yarn add jest jest-environment-jsdom babel-jest @babel/preset-env @vue/vue3-jest @vue/test-utils -D

# npm
> npm install --save-dev jest jest-environment-jsdom babel-jest @babel/preset-env @vue/vue3-jest @vue/test-utils

# 支持ts
> yarn add --dev ts-jest @types/jest

jest.config.js

module.exports = {
  preset: 'ts-jest',
  globals: {},

  // 测试运行环境,jsdom类浏览器环境
  testEnvironment: "jsdom",

  transform: {
    "^.+\.vue$": "@vue/vue3-jest",
    // "^.+\\.vue$": "vue-jest",
    "^.+\js$": "babel-jest",
    "^.+\\.ts$": "ts-jest"
  },

  testRegex: "(/__tests__/.*|(\.|/)(test|spec))\.(js|ts)$",
  moduleFileExtensions: ['vue', 'js', 'json', 'jsx', 'ts', 'tsx', 'node'],
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
  },
  coveragePathIgnorePatterns: ["/node_modules/", "/tests/"],
  coverageReporters: ["text", "json-summary"],

  // Fix in order for vue-test-utils to work with Jest 29
  // https://test-utils.vuejs.org/migration/#test-runners-upgrade-notes
  testEnvironmentOptions: {
    customExportConditions: ["node", "node-addons"],
  },

  snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
  testURL: 'http://localhost/',
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname',
  ],
  coverageDirectory: 'coverage',
  collectCoverageFrom: ['<rootDir>/src/**/*.{js,ts,vue}'],
  coveragePathIgnorePatterns: ['^.+\\.d\\.ts$', 'src/runtimeEnv.ts'],
  modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'],
  cacheDirectory: '<rootDir>/tmp/cache/jest',
  timers: 'fake',
}

babel.config.js

module.exports = {
  env: {
    test: {
      presets: [
        [
          "@babel/preset-env",
          {
            targets: {
              node: "current",
            },
          },
        ],
      ],
    },
  },
}

package.json

{
  "scripts": {
		"test:unit": "jest",
    "": "jest --watch",  # 默认执行 jest -o 监视有改动的测试
    "": "jest --watchAll" # 监视所有测试
  }
}

部署Vue系列单元测试

部署React系列单元测试

Last Updated:
Contributors: 709992523, Eshen