Jest 001: 安装和匹配器

631 阅读2分钟

Jest 是一款 JavaScript 测试框架,它很简洁。使用 Jest 的项目有 Babel, TypeScript, Node, React, Angular, Vue 等。

起步

安装 Jest

$ pnpm add --save-dev jest

创建 sum.js 文件:

function sum(a, b) {
    return a + b;
}
module.exports = sum;

创建单元测试文件 sum.test.js

const sum = require('./sum');

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

修改 package.json 文件,新增 test 脚本:

{
    "scripts": {
        "test": "jest"
    }
}

最后,执行 pnpm test,Jest 会输出测试信息:

 PASS  ./sum.test.js
  ✓ adds 1 + 2 to equal 3 (1 ms)

恭喜 🎉,你已经成功编写了第一个 Jest 测试!

测试中的 expecttoBe 用于确保两个值完全相同。

更多设置

创建基本配置文件

$ pnpm jest --init

使用 Babel

首先安装必要的依赖:

$ pnpm add --save-dev babel-jest @babel/core @babel/preset-env

设置 Babel,使用当前版本的 Node。在根目录创建 babel.config.js 文件:

// babel.config.js
module.exports = {
    presets: [
        ['@babel/preset-env', { targets: { node: 'current' } }]
    ];
};

使用 ESM

Jest 默认使用 CJS 模块规范。如果想在代码中使用 ESM 模块,可以如上所示配置 Babel。因为 Jest 自动识别 Babel 配置,会把 ESM 转换为 CJS 执行。

另外,也可以开启原生的 Node.js ESM 规范。具体步骤如下所示。

Jest 目前对于 ESM 的支持还是实验状态,目前的做法如下:

  1. 修改 package.json。新增 "type": "module",将 Node.js 的默认模块改为 ESM;
  2. "test" npm scripts 改成:"test": "NODE_OPTIONS=--experimental-vm-modules jest"
  3. 如果已经生成 jest.config.js,同样需要将其改为 ESM 格式;

🚨 注意,在 Windows 系统下,需要借助 cross-env 设置环境变量。相应的 npm scripts 要改为如下形式:

{
    "scripts": {
        "test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest"
    }
}

智能提示

如果编写 Jest 测试文件时,编辑器没有出现智能提示,可以安装 @types/jest

$ pnpm add --save-dev @types/jest

使用匹配器

Jest 的“匹配器(matchers)”提供了丰富多样的测试手段。

常用匹配器

最简单的测试方法就是严格相等:

test('two plus two is four', () => {
    expect(2 + 2).toBe(4);
});

toBe 使用 Object.is 测试严格相等性。如果要检查 object 的值,需要使用 toEqual

test('object assignment', () => {
    const data = { one: 1 };
    data['two'] = 2;
    expect(data).toEqual({ one: 1, two: 2 });
});

toEqual 递归地检查对象或数组的每个字段。

也可以检查匹配器的对立面:

test('adding positive numbers is not zero', () => {
    for (let a = 1; a < 10; a++) {
        for (let b = 1; b < 10; b++) {
            expect(a + b).not.toBe(0);
        }
    }
});

真值判断

在测试中,有时需要区别 undefined, nullfalse,但有时也需要对这些值统一处理。Jest 包含一些辅助函数,提供了明确的真值判断。

  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefinedtoBeUndefined 的对立面
  • toBeTruthy 匹配所有 if 语句认为是真的值
  • toBeFalsy 匹配所有 if 语句认为是假的值

比如:

test('null', () => {
    const n = null;
    expect(n).toBeNull();
    expect(n).toBeDefined();
    expect(n).not.toBeUndefined();
    expect(n).not.toBeTruthy();
    expect(n).toBeFalsy();
});

test('zero', () => {
    const z = 0;
    expect(z).not.toBeNull();
    expect(z).toBeDefined();
    expect(z).not.toBeUndefined();
    expect(z).not.toBeTruthy();
    expect(z).toBeFalsy();
});

尽量使用贴近代码实现的匹配器。

数字

常见的数学运算都有对应的匹配函数:

test('two plus two', () => {
    const value = 2 + 2;
    expect(value).toBeGreaterThan(3);
    expect(value).toBeGreaterThanOrEqual(3.5);
    expect(value).toBeLessThan(5);
    expect(value).toBeLessThanOrEqual(4.5);
    
    // 对于数字,toBe 和 toEqual 没分别
    expect(value).toBe(4);
    expect(value).toEqual(4);
});

对于浮点数的相等性判断,使用 toBeCloseTo 代替 toEqual,避免浮点精度导致的判断失误。

test('adding floating point numbers', () => {
    const value = 0.1 + 0.2;
    expect(value).toBeCloseTo(0.3);
});

字符串

可以在 toMatch 中使用正则表达式检测字符串:

test('there is no I in team', () => {
    expect('team').not.toMatch(/I/);
});

test('but there is a "stop" in Christoph', () => {
    expect('Christoph').toMatch(/stop/);
});

数组和可迭代对象

使用 toContain 判断数组或可迭代对象(iterable)是否包含特定元素:

const shoppingList = [
    'diapers',
    'milk',
];

test('the shopping list has milk on it', () => {
    expect(shoppingList).toContain('milk');
    expect(new Set(shoppingList)).toContain('milk');
});

异常

使用 toThrow 判断某个函数是否抛出异常:

function compileAndroidCode() {
    throw new Error('you are using the wrong JDK');
}

test('compiling android goes as expected', () => {
    expect(() => compileAndroidCode()).toThrow();
    expect(() => compileAndroidCode()).toThrow(Error);
    
    // 还可以使用具体的报错信息或正则表达式
    expect(() => compileAndroidCode()).toThrow('you are using the wrong SDK');
    expect(() => compileAndroidCode()).toThrow(/JDK/);
});

注意:抛出异常的函数需要被新函数包裹,否则会导致 toThrow 断言失效。

下一步,认识测试异步代码的四种方法。

参考文档

  1. Getting Started - Jest
  2. Using Matchers - Jest