Jest 的使用和语法

500 阅读5分钟

Jest 的介绍

Jest 是 Facebook 出品的一个测试框架,相对其他测试框架,其一大特点就是内置了常用的测试工具,比如自带断言、Mock 功能、测试覆盖率工具,实现开箱即用。

Jest 的安装

安装

Jest 可以通过 npm 或 yarn 进行安装。以 npm 为例,既可使用 npm install -g jest 进行全局安装,也可以进行局部安装并在 package.json 中指定 test 脚本。

// pageage.json
"scripts": {
    "test": "jest"
 }

Jest 支持三种方式写测试代码:

  • .spec.js
  • .test.js
  • 放在 __test__ 文件夹下

当 Jest 全局运行或是通过运行 npm run test,它都会执行当前目录下所有的 *.test.js*.spec.js 文件,完成测试。

vscode 中 Jest 智能提示

由于文件中并没有引入 Jest 的方法,所以使用的时候 vscode 没有提供智能提示。

可以通过安装 jest 的类型声明文件 @types/jest 来解决。npm install @type/jest -D

解决报错

执行 cnpm run test 时有可能会发生下面的报错:

Test suite failed to run

Jest encountered an unexpected token

Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.        

Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

By default "node_modules" folder is ignored by transformers.

Here's what you can do:
....

执行 npm install babel-plugin-transform-es2015-modules-commonjs -D,并在 .babelrc 文件中进行如下配置:

{
  "plugins": [
    "transform-es2015-modules-commonjs"
  ]
}

Jest 的使用方法

基本方法

Jest 的基本方法分为 3 类 ,分别是

  • 分组(Test Group):descripe
  • 测试用例(Test Case):test
  • 断言(Assert):expect(运行需要测试的方法并返回实际结果).toBe(预期结果)

匹配器(Matchers)

先看下一段最简单的测试代码

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

在此代码中,expect(2 + 2) 返回的是一个“期望”对象,.toBe(4)是匹配器。 当 Jest 运行时,它会跟踪所有失败的匹配器,方便我们打印出错误信息。

在编写测试时,我们通常需要检查值是否需要满足某些条件,Jest 中提供的 expect 允许我们访问很多“Matcher”。这些“匹配器”就是帮助我们来验证返回结果是否正确的。 常用的匹配器有如下:

常用匹配器

.toBe(value)

.toBe(value) 和 Object.is() 是等同的。用于比较原始值或检查对象实例的引用身份。

例如,此代码将验证 can 对象的某些属性:

const can = {
    name: 'pamplemousse',
    ounces: 12,
};

describe('the can', () => {
    test('has 12 ounces', () => {
        expect(can.ounces).toBe(12);
    });

    test('has a sophisticated name', () => {
        expect(can.name).toBe('pamplemousse');
    });
});
.toEqual(value)

.toEqual(value) 递归比较对象实例的所有属性。

const can1 = {
    flavor: 'grapefruit',
    ounces: 12,
};
const can2 = {
    flavor: 'grapefruit',
    ounces: 12,
};

describe('the La Croix cans on my desk', () => {
    test('have all the same properties', () => {
        expect(can1).toEqual(can2);
    });
    test('are not the exact same can', () => {
        expect(can1).not.toBe(can2);
    });
});

判断真假、空值

在测试中,我们有时需要区分 undefined、null 和 false

  • toBeNull 仅匹配 null
  • toBeUndefined 仅匹配 undefined
  • toBeDefined 是相反的 toBeUndefined,也就是匹配非 undefined
  • 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();
});

判断数字

  • toBeGreaterThan(number | bigint) 用于比较 received 大于 expected 的数字或大整数值
  • toBeGreaterThanOrEqual(number | bigint) 用于比较 received 大于或等于 expected 的数字或大整数值
  • toBeLessThan(number | bigint) 用于比较 received 小于 expected 的数字或大整数值
  • toBeLessThanOrEqual(number | bigint) 用于比较 received 大于或等于 expected 的数字或大整数值

判断字符串

我们可以使用 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 来检查数组或可迭代对象是否包含特定项

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);

    // You can also use the exact error message or a regexp
    expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
    expect(() => compileAndroidCode()).toThrow(/JDK/);
});

注意:抛出异常的函数需要在包装函数中调用,否则 toThrow 断言将失败。

Expect

expect(value)

expect 每次要测试值时都会使用该函数。

expect.assertions(number)

expect.assertions(number) 验证在测试期间是否调用了一定数量的断言。这在测试异步代码时通常很有用,以确保回调中的断言实际被调用。

例如,假设我们有一个 doAsync 接收两个回调 callback1 和 callback2

test('doAsync calls both callbacks', () => {
    expect.assertions(2);
    function callback1(data) {
        expect(data).toBeTruthy();
    }
    function callback2(data) {
        expect(data).toBeTruthy();
    }

    doAsync(callback1, callback2);
});

该 expect.assertions(2) 调用确保两个回调都被实际调用

expect.hasAssertions()

expect.hanAssertions() 验证在测试期间至少调用了一个断言。这在测试异步代码时通常很有用,以确保回调中的断言实际被调用。

test('prepareState prepares a valid state', () => {
    expect.hasAssertions();
    prepareState(state => {
        expect(validateState(state)).toBeTruthy();
    });
    return waitOnState();
});

其他

Jest 中 test() 和 it() 方法的区别

jest 文档 中可以看到 test 方法的描述,test 方法的别名也叫 it 方法。

test(name, fn, timeout)

test('did not rain', () => {
    expect(inchesOfRain()).toBe(0);
});

我们在 jest的源码 中可以看到以下代码:

export interface TestFrameworkGlobals {
    it: ItConcurrent;
    test: ItConcurrent;
    fit: ItBase & { concurrent?: ItConcurrentBase };
    xit: ItBase;
    xtest: ItBase;
    describe: Describe;
    xdescribe: DescribeBase;
    fdescribe: DescribeBase;
    beforeAll: HookFn;
    beforeEach: HookFn;
    afterEach: HookFn;
    afterAll: HookFn;
}

通过以上代码我们知道 test() 和 it() 是一样的,因为他们使用的是相同的接口,都是 ItConcurrent

那么,Jest 为什么需要 test() 和 it() 两个相同的方法呢?答案是 为了熟悉我们从其他框架迁移