Jest 零基础入门

33,096 阅读22分钟

一、引言

前端这几年发展的非常迅速,我们的系统的功能正在变的越来越复杂,这对我们的前端工程化能力提出了更高的要求,听到工程化,大家的第一反应肯定是高质量的代码设计高质量的代码实现

但实际上,前端自动化测试也是前端工程化里面非常重要的一个环节。

二、Jest 基础入门

一个普通前端听到自动化测试,第一反应可能是:我工作这么多年也没写过测试,这个东西有用吗?

答:非常有用

如果你打开GitHub,去看一下流行的开源库或者框架的源码,你会发现,在这些源码里面,全部都包含了大量的自动化测试的代码。比如antdlodash、再比如vuereactechartsredux等等……

开源的工具需要稳定性,而引入前端自动化测试为开源项目提供稳定性,是再好不过的选择了。

三、学习前提

阅读这篇文章需要以下知识储备:

  • js、es6 基础语法
  • node、npm 相关知识
  • git 的相关操作
  • react或者vue,至少了解一个
  • 状态管理工具,至少了解一个

四、背景及原理

首先在任意目录下创建一个math.js文件,假设这个文件是一个数学库,里面定义两个函数,分别是加法和减法:

// math.js

function add(a, b) {
  return a + b;
}

function minus(a, b) {
  return a - b;
}

这时候我们可以在业务代码里去使用这个数学库。

但是,假如,上面的minus函数我们不小心写错了,把减法写成了乘法,如果直接在业务代码中使用这个方法,就会带来无法预期的bug。

所以这时候,我们就需要对math.js这个公共库进行自动化测试,确保没问题之后,再让业务组件去调用,这样就会保证不会出特别多的bug了。

我们可以这样做:

在该目录下创建一个math.test.js文件,然后写一点测试代码:

const result = add(3, 7);
const expect = 10;

if (result !== expect) {
  throw new Error(`3 + 7 应该等于${expect},结果却是${result}`);
}
const result = minus(3, 3);
const expect = 0;

if (result !== expect) {
  throw new Error(`3 - 3 应该等于${expect},结果却是${result}`);
}

这时候我们运行这段代码,会发现没有抛出任何异常,说明这两个测试用例都通过了。

这就是自动化测试最原始的雏形。

然后我们思考一个问题,如何将这堆代码进行简化,做成一个公用的函数,比如这样:

// 测试 3 + 3 是否等于 6
expect(add(3, 3)).toBe(6);

// 测试 3 - 3 是否等于 0
expect(minus(3, 3)).toBe(0);

expect 方法实现:

function expect(result) {
  return {
    toBe(actual) {
      if (result !== actual) {
        throw new Error("预期值和实际值不相等");
      }
    },
  };
}

这时候我们运行这段代码,会发现没有抛出任何异常,说明这两个测试用例都通过了。

虽然实现了 expect 函数,但是报错的内容始终是一样的,我们不知道是具体哪个方法出现了问题,这时候我们就会想到,我们需要将这个 expect 方法进一步做改良,我们如果能在 expect 方法外部再包装一层,就可以多传递一些额外的内容,比如创造这样的写法:

test("测试加法 3 + 3", () => {
  expect(add(3, 3)).toBe(6);
});

test("测试减法 3 - 3", () => {
  expect(minus(3, 3)).toBe(0);
});

这样封装之后,我们既能进行测试,又能得到测试的描述。

test 方法实现:

function test(desc, fn) {
  try {
    fn();
    console.log(`${desc} 通过测试`);
  } catch {
    console.log(`${desc} 没有通过测试`);
  }
}

所以前端自动化测试到底是什么?

答:实际上就是写了一段其它的用来测试的js代码,通过测试代码去运行业务代码,判断实际结果是否满足预期结果,如果满足,就是没有问题,如果不满足,就是有问题。

上面实现的 expect 方法test 方法 实际上和主流的前端自动化测试框架 jest 里面的语法是完全一致的。所以上面的示例代码可以理解为 jest 的底层实现原理。

五、Jest 框架入门

在上面的部分,我们实现了 expect 方法 和 test 方法。

在实际项目的自动化测试过程中,如果只有这两个方法,很显然,是远远不够的,这时候,就需要我们对之前的方法进行扩充,同时还有很多自动化的机制需要集成进去。

这时候!Jest 闪亮登场!

5.1 Jest 框架介绍

一个优秀的自动化测试框架,在以下三个方面应该比较突出:

  • 性能好
  • 功能齐全
  • 易用性很好

Jest 在这三个方面做的非常好(当然了,像Jasminemocha这些主流前端测试框架做的也不错)。

这些主流前端测试框架使用的方法、原理上都差不多,实际上,我们学会了一个,另外几个也就很容易的继续学习了,所以这篇文章只讲解 Jest。

那么 Jest 具体好用在哪些方面呢?

答案是这些:

  • 速度快(可以自动监测修改过的代码,不会重复测试)
  • API 简单、数量少
  • 易配置
  • 隔离性好
  • 监控模式
  • IDE 整合(比如vs code)
  • Snapshot(快照测试)
  • 多项目并行
  • 覆盖率
  • Mock 丰富
  • 对新技术支持度好

5.2 使用 Jest 修改自动化测试样例

在上面的部分,我们自己写了一些测试代码对 Math 库进行了测试,现在我们要使用 Jest 对 Math 进行重新测试。

首先打开编辑器,进入到控制台(为了避免不必要的麻烦,建议大家跟我一样使用vs code):

第1步:初始化npm环境:

npm init

在执行命令的时候会遇到一些选项或者问题,无脑按回车即可,如果看到目录下出现了一个 package.json 文件,那么这就是一个标准的 npm 包了。

第2步:安装依赖

npm install jest -D

(注:-D 是 --save -dev 的缩写)

如果看到 Jest 出现在了 package.json 文件中,并且目录中出现了 node_modules 文件夹,就说明安装成功了。

第3步:修改之前的代码

首先删除之前写的 test 方法expect 方法 的实现过程,因为 Jest 自带了这两个方法,我们不需要手动实现。然后采用模块化的标准,将 math.js 中的方法导出:

// math.js

function add(a, b) {
  return a + b;
}

function minus(a, b) {
  return a - b;
}

function multi(a, b) {
  return a * b;
}

module.exports = { add, minus, multi };

然后在 math.test.js 文件中引入这几个方法:

// math.test.js

const { add, minus, multi } = require("./math");

test("测试加法 3 + 3", () => {
  expect(add(3, 3)).toBe(6);
});

test("测试减法 3 - 3", () => {
  expect(minus(3, 3)).toBe(0);
});

test("测试乘法 3 * 3", () => {
  expect(multi(3, 3)).toBe(9);
});

第3步:配置 package.json 文件

将如下代码添加到 package.json 中:

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

第4步:执行测试代码

完成配置之后,运行 yarn test 或者 npm run test ,控制台将输出如下信息:

222.png

假设这个时候,我们把第三个测试用例的结果改成错误值:

// math.test.js

const { add, minus, multi } = require("./math");

test("测试加法 3 + 3", () => {
  expect(add(3, 3)).toBe(6);
});

test("测试减法 3 - 3", () => {
  expect(minus(3, 3)).toBe(0);
});

test("测试乘法 3 * 3", () => {
  expect(multi(3, 3)).toBe(10); // 错误的结果
});

重新运行 yarn test 或者 npm run test,可以看到如下结果:

333.png

可以看到,控制台报出了错误:

乘法测试没有通过,期待值(Expected)是10,但是返回值是(Received)9。

然后我们再把测试结果改成正确的值,重新执行命令行,就能得到正确的测试结果了。

这个时候,说明Jest 这个框架已经被我们正确的使用了。

5.3 Jest 的简单配置

JestWebpack 一样都有默认配置,我们可以运行 npx jest --init 命令来 初始化默认配置。在运行命令行的时候,会弹出一些选项,比如:

  • 是否需要开启typescript
  • 是否需要生成覆盖率报告
  • 选择node 环境或者浏览器环境
  • 在测试完成之后,是否需要进行一些清除工作
  • ……

这些选项可以根据自己的实际情况进行选择或者

444.png

当全部选择完成之后,可以看到在目录中多了一个 jest.config.js 文件,这时候就说明初始化配置完成了。点击这里可以查看配置文档。

1. 代码测试覆盖率:

当我们在控制台中输入npx jest --coverage时,控制台会打印出以下信息:

aa.png

这是一个关于测试覆盖率的说明,这是一个图表信息,它告诉我们所有的文件都被100%的测试到了。

如果觉得执行npx jest --coverage命令太麻烦,你也可以将如下代码添加到 package.json 中:

"scripts": {
  "test": "jest",
  "coverage": "jest --coverage"
},

然后可以执行npm run coverage,和执行npx jest --coverage是一样的效果。

除了控制台可以看到图表信息之外,你还可以在当前目录下看到一个生成的coverage目录

bb.png

在里面有一个lcov-report文件夹,里面有一个index.html,我们打开它会看到:

55.png

可以看到,这是一个通过Jest生成的非常漂亮的html页面,上面会告诉我们,我们的每一个文件的测试到底覆盖了多少代码。可以看到,math.js 中的代码都被100%的覆盖到了。

通过这个例子,我们就理解了code coverage(代码覆盖率)的意思了。

总结来说:测试覆盖率就是我们编写的测试代码对原来的功能代码的占比。

tips: 配置文件中的coverageDirectory就是代码测试覆盖率生成报告所在的文件夹的名字:

module.exports = {
  coverageDirectory: "abc"
};

例如,我们让coverageDirectory的值为abc,那么生成的测试报告就在目录abc下面。

2. 把 commonjs 改成 es module

我们先把 math.jsmath.test.js 中的代码改成 es module 的规范:

// math.js

export function add(a, b) {
  return a + b;
}

export function minus(a, b) {
  return a - b;
}

export function multi(a, b) {
  return a * b;
}
// math.test.js

import { add, minus, multi } from "./math";

test("测试加法 3 + 3", () => {
  expect(add(3, 3)).toBe(6);
});

test("测试减法 3 - 3", () => {
  expect(minus(3, 3)).toBe(0);
});

test("测试乘法 3 * 3", () => {
  expect(multi(3, 3)).toBe(9);
});

然后运行 npm run test,会发现控制台报错了。

要解决这个问题,需要用到 Babel 把代码进行转化就OK了,如果想让 Babel 支持 ESM,我们需要三个包:

  • @babel/core:babel核心库
  • @babel/preset-env:进行 ES 语法转换的库
  • babel-jest:和 Jest 通信的库,用来检测是否安装了上面两个依赖

但是因为我们在安装 Jest 的时候 babel-jest 就默认被安装了,所以我们只需要安装剩下两个包就行了。

安装依赖:

npm install @babel/core @babel/preset-env -D

(注:-D 是 --save -dev 的缩写)

安装完成之后,在项目的根目录下创建一个 .babelrc 配置文件:

zzz.png

创建完成之后,将如下代码添加到 .babelrc 中:

{
  "presets": [["@babel/preset-env", { "targets": { "node": "current" } }]]
}

保存之后,重新运行 npm run test,执行测试用例即可。

5.4 Jest 中的匹配器(matchers)

众所周知,在前面的代码中,我们在编写一个测试用例的时候用到了 testexpecttoBe 三个方法。

这种语法大概可以翻译为:期待一个值的结果去匹配另一个值

比如:

expect(1 + 1).toBe(2);

这个例子可以翻译为:期待 1 + 1 的结果是 2,这里的 toBe 就是一个匹配器,用来判断接收值期望值是否相等。

事实上,在 Jest 中,除了 toBe 之外,还有很多匹配器

  • toBe:测试两个对象的值是否相等,类似于 js 中的 === 运算符
// toBe

test("测试加法交换律", () => {
  for (let x = 0; x <= 10; x++) {
    for (let y = 0; y <= 10; y++) {
      expect(x + y).toBe(y + x);
    }
  }
});
  • toEqual:测试两个对象的原始值是否相等,只检查内容,不检查引用
// toEqual

const can1 = { value: "hello" };
const can2 = { value: "hello" };

test("测试 can1 和 can2 的内容是否相等", () => {
  expect(can1).toEqual(can2);
});
  • toBeNull:测试对象的值是否为null,效果相当于.toBe(null)
// toBeNull

const value = null;

test("测试值是否为 null", () => {
  expect(value).toBeNull();
});
  • toBeUndefined:测试对象的值是否为undefined,效果相当于.toBe(undefined)
// toBeUndefined

const value = undefined;

test("测试值是否为 undefined", () => {
  expect(value).toBeUndefined();
});
  • toBeDefined:测试值是否被定义过,除了undefined之外都会通过测试
// toBeDefined

const value = 1;

test("测试值是否被定义过", () => {
  expect(value).toBeDefined();
});
  • toBeTruthy:检查值转成布尔值之后是否为真值
  • toBeFalsy:检查值转成布尔值之后是否为假值
// toBeTruthy、toBeFalsy

test("测试是否为真值", () => {
  expect(0).toBeTruthy(); // 不通过
  expect("").toBeTruthy(); // 不通过
  expect(null).toBeTruthy(); // 不通过
  expect(false).toBeTruthy(); // 不通过
  expect(undefined).toBeTruthy(); // 不通过
});

test("测试是否为假值", () => {
  expect(0).toBeFalsy(); // 通过
  expect("").toBeFalsy(); // 通过
  expect(null).toBeFalsy(); // 通过
  expect(false).toBeFalsy(); // 通过
  expect(undefined).toBeFalsy(); // 通过
});
  • not:取反匹配器,相当于 js 中的 ! 运算符
// not

test("测试值是否不为 aaa", () => {
  expect("hello").not.toBe("aaa");
});

test("测试值是否不为 null", () => {
  expect([]).not.toBeNull();
});

test("测试值是否不为 undefined", () => {
  expect({}).not.toBeUndefined();
});
  • toBeGreaterThan:检查接收值是否大于期待值
// toBeGreaterThan

test("测试 10 是否大于 9", () => {
  expect(10).toBeGreaterThan(9);
});
  • toBeLessThan:检查接收值是否小于期待值
// toBeLessThan

test("测试 10 是否小于 20", () => {
  expect(10).toBeLessThan(20);
});
  • toBeGreaterThanOrEqual:检查接收值是否大于等于期待值
// toBeGreaterThanOrEqual

test("测试 10 是否大于等于 10", () => {
  expect(10).toBeGreaterThanOrEqual(10);
});
  • toBeLessThanOrEqual:检查接收值是否小于等于期待值
// toBeLessThanOrEqual

test("测试 10 是否小于等于 10", () => {
  expect(10).toBeLessThanOrEqual(10);
});
  • toBeCloseTo:检查浮点数是否接近(是否近似相等)
// toBeCloseTo

test("测试 0.1 + 0.2 是否等于 0.3", () => {
  expect(0.1 + 0.2).toBe(0.3); // 不通过
  expect(0.1 + 0.2).toBeCloseTo(0.3); //通过
});
  • toMatch:检查值是否和字符串或者正则相匹配
// toMatch

test("测试字符串是否包含 baidu", () => {
  expect("www.baidu.com").toMatch("baidu");
  expect("www.baidu.com").toMatch(/baidu/);
});
  • toContain:检查数组中是否包含某一项(类似于 js 中的 includes 方法)
// toContain

test("测试 list 中是否包含 3", () => {
  const list = [1, 2, 3];
  expect(list).toContain(3);
});
  • toThrow:测试函数在调用时是否有异常抛出
// toThrow

const fn1 = () => {
  console.log("hello");
};

const fn2 = () => {
  throw new Error("this is a new err");
};

test("测试 fn1、fn2 调用时是否有异常", () => {
  expect(fn1).toThrow(); // 不通过
  expect(fn2).toThrow(); // 通过
});

Jest 中,除了上面这些列举出来的常用的匹配器之外,还有很多匹配器为我们提供。不需要记住所有,知道常用的几个即可。如果想了解更多,可以点击这里查看官方文档

5.5 Jest 命令行工具的使用

进入到 package.json 文件中,在 test 命令行后面加上 --watchAll 参数:

"scripts": {
  "test": "jest --watchAll",
  "coverage": "jest --coverage"
},

这样做的好处是,我们不需要每次都执行 npm run test,在第一次执行之后,进程会自动监听测试用例的变化,如果测试用例代码发生了变化,会自动执行。

运行npm run test,会看到如下选项:

kk.png

可以看到,进入到 Watch 模式中,有很多快捷键,这些快捷键的作用如下:

  • f键:只运行失败的测试用例
  • o键:只运行发生更改的测试用例
  • p键:按文件名regex模式进行筛选
  • t键:按测试名称regex模式进行筛选
  • q键:退出监测模式
  • Enter键:触发测试用例

Jest 中有很多命令行工具,合理的使用命令行,会让我们的测试变的更加灵活、好用。

5.6 异步代码的测试

要测试异步代码,需要先编写异步代码,安装 axios,安装好 axios 之后,不管是在node环境还是浏览器环境,我们都可以发送请求了:

npm install axios --save

首先准备一个接口,这个接口的返回值是一个json对象:

{
  "success": true
}

然后引入axios,编写一个异步函数,并且导出:

import axios from "axios";

export const getData = () => {
  return axios.get("http://www.dell-lee.com/react/api/demo.json");
};

在测试异步函数的时候,需要这样写:

方法一:Promises 规范

测试成功的返回值:

import { getData } from "./index";

test("测试 getData 的返回值为 { success: true }", () => {
  return getData().then(res => {
    expect(res.data).toEqual({ success: true });
  });
});

在测试成功返回值的时候,需要在.then里面执行测试用例,而且必须在开头加上return,返回整个异步代码,否则这个测试是没有意义的。

如果你忽略了这个return,你的测试将在异步函数返回的 promise 解析之前完成。

测试失败的返回值:

import { getData } from "./index";

test("测试 getData 的返回值包含 404", () => {
  return getData().catch(err => {
    expect.assertions(1);
    expect(err.toString()).toMatch("404");
  });
});

在测试失败返回值的时候,需要在.catch里面执行测试用例,但是这样做的话,如果请求发送成功,异步函数走进了.then回调,.catch里面的内容不会被执行,相当于这个测试用例没有做任何事情,还是照样能通过测试。

要解决这个问题,需要在前面加上expect.assertions(1);,用来断言这个测试用例调用了一定数量的expect。如果调用次数不够,测试用例就不会通过。

方法二:.resolves 匹配器 / .rejects 匹配器

测试成功的返回值:

import { getData } from "./index";

test("测试 getData 的返回值为 { success: true }", () => {
  return expect(getData()).resolves.toEqual({ success: true });
});

你也可以在 expect 语句中使用.resolves匹配器,Jest将等待该 Promises 函数resolve

如果 Promises 函数reject,测试用例将自动失败。

测试失败的返回值:

import { getData } from "./index";

test("测试 getData 的返回值包含 404", () => {
  return expect(getData()).rejects.toMatch("404");
});

同理,你也可以在 expect 语句中使用.rejects匹配器,Jest将等待该 Promises 函数reject

如果 Promises 函数resolve,测试用例将自动失败。

方法三:Async / Await

测试成功的返回值:

import { getData } from "./index";

test("测试 getData 的返回值为 { success: true }", async () => {
  const { data } = await getData();
  expect(data).toEqual({ success: true });
});

你也可以像编写普通的 js 代码那样,给 test 方法的回调函数传递 async 关键字,在 expect 语句前使用await关键字调用异步函数拿到返回值。

测试失败的返回值:

import { getData } from "./index";

test("测试 getData 的返回值包含 404", async () => {
  expect.assertions(1);
  try {
    await getData();
  } catch (err) {
    expect(err.message).toMatch("404");
  }
});

同理,如果需要测试失败的返回值,你需要使用原生 js 中的 try / catch 语句来捕获异常,同时配合 expect.assertions(1); 用来校验这个测试用例调用了一定数量的expect。如果调用次数不够,说明这里没有捕获到异常,测试用例就不会通过。

方法四:Async、Await 和 .resolves、.rejects 结合使用

import { getData } from "./index";

test("测试 getData 返回成功", async () => {
  await expect(getData()).resolves.toEqual({ success: true });
});

test("测试 getData 返回失败", async () => {
  await expect(getData()).rejects.toMatch("404");
});

在这种情况下,asyncawaitpromise 示例使用的逻辑是相同的语法糖。

5.7 Jest 中的钩子函数

通常,在编写测试时,你需要在测试运行之前进行一些初始化工作,并且需要在测试运行之后进行一些完成工作。Jest提供了钩子函数来处理这个问题。

我们通过一个 计数器 来学习钩子函数相关的知识。

首先在index.js里面定义一个类,并且导出:

// index.js

class Counter {
  constructor() {
    this.number = 0;
  }
  add() {
    this.number++;
  }
  minus() {
    this.number--;
  }
}

export default Counter;

可以看到,这是一个使用 ES6 语法定义的 class

  • 在实例化的时候定义了number,初始值是0
  • 定义了静态方法add,执行结果为number1
  • 定义了静态方法minus,执行结果为number1

然后在index.test.js里面引入这个类,测试add方法:

// index.test.js

import Counter from "./index";

const counter = new Counter();

test("测试 Counter 的 add 方法", () => {
  expect(counter.number).toBe(0);
  counter.add();
  expect(counter.number).toBe(1);
});

然后运行测试用例,完美通过。

然后继续添加minus方法的测试用例:

// index.test.js

import Counter from "./index";

const counter = new Counter();

test("测试 Counter 的 add 方法", () => {
  expect(counter.number).toBe(0);
  counter.add();
  expect(counter.number).toBe(1);
});

test("测试 Counter 的 minus 方法", () => {
  expect(counter.number).toBe(0);
  counter.minus();
  expect(counter.number).toBe(-1);
});

然后运行测试用例,结果为:

  • 测试 add 方法:通过
  • 测试 minus 方法:不通过

出现这个情况的原因是:

我们的实例化过程写在了测试用例外面,所有的测试用例公用一个counter 实例,所以在测试 add 方法的时候,我们已经对实例的number属性做出了修改。导致minus方法的测试用例没用通过。

解决办法是:

我们可以把实例化写在每一个测试用例里面,每次测试都创建一个新的counter 实例。这样就不会公用一个counter了,也不会影响到其它实例了。

但是一般情况下,我们不会这样做,因为测试用例很多的话,每次都创建一个 counter实例 是一件很麻烦的事情,假如当前文件中有1000个和counter有关的测试用例,那么就要创建1000counter实例。

这个时候!钩子函数派上用场了!

// index.test.js

import Counter from "./index";

let counter = null;

beforeEach(() => {
  counter = new Counter();
});

test("测试 counter 的 add 方法", () => {
  expect(counter.number).toBe(0);
  counter.add();
  expect(counter.number).toBe(1);
});

test("测试 counter 的 minus 方法", () => {
  expect(counter.number).toBe(0);
  counter.minus();
  expect(counter.number).toBe(-1);
});

然后运行测试用例,结果通过。

beforeEach() 的作用是在每个测试用例执行之前执行里面的回调函数,如果你需要在测试开始之前对很多个测试做一些重复的工作,比如要初始化状态,你就可以使用它。

实际上,Jest 一共有四个钩子函数:

  • beforeAll:在所有测试用例执行之前调用(调用一次)
  • afterAll:在所有测试用例执行之后调用(调用一次)
  • beforeEach:在每个测试用例执行之前调用(调用多次)
  • afterEach:在每个测试用例执行之后调用(调用多次)

5.8 钩子函数的作用域

在了解作用域之前,需要先了解一个小知识点:Scoping

默认情况下,beforeAllafterAll应用于文件中的每个测试用例。

实际上,还可以使用describe方法将测试用例进行分组。当它们位于describe中时,beforeAllafterAll只应用于当前分组中的测试用例。

// index.test.js

describe("测试分组1", () => {
  beforeAll(() => {
    console.log("测试分组1 - beforeAll");
  });
  afterAll(() => {
    console.log("测试分组1 - afterAll");
  });
  test("测试", () => {
    console.log("测试分组1 测试");
    expect(1 + 1).toBe(2);
  });
});

describe("测试分组2", () => {
  beforeAll(() => {
    console.log("测试分组2 - beforeAll");
  });
  afterAll(() => {
    console.log("测试分组2 - afterAll");
  });
  test("测试", () => {
    console.log("测试分组2 测试");
    expect(1 + 1).toBe(2);
  });
});

执行上面代码:

WX20211230-104011@2x.png

在默认情况下,Jest将按照describe的顺序连续运行所有测试分组,等待每个测试完成后再继续。

需要注意的是:

  • 如果我们不进行分组,相当于在最外面写了一层describe
  • 除此之外,实际上,在describe里面还能嵌套describe,就像下面这样:
// index.test.js

describe("第一层", () => {
  beforeAll(() => console.log("第一层 - beforeAll"));
  describe("第二层", () => {
    beforeAll(() => console.log("第二层 - beforeAll"));
    describe("第三层", () => {
      beforeAll(() => console.log("第三层 - beforeAll"));
      test("测试", () => {
        console.log("测试");
        expect("hello" + " " + "world").toBe("hello world");
      });
    });
  });
});

运行这个测试,可以看到下面结果:

aaa.png

所以可以得出一些结论:

  • 每一个 describe 都可以有自己的钩子函数
  • 每一个 describe 都有自己的作用域
  • 每一个 钩子函数也有自己的作用域,就是当前所在的 describe
  • 每一个 describe 里面的钩子函数对自己作用域下面所有的测试用例都生效
  • 如果 describe 是多层嵌套的,那么测试用例执行的顺序是由外到内

最后再补充一个知识点:

如果有·、多个测试用例,但是只想运行一个的时候,注释掉其它的测试用例往往不是最好的选择,我们可以使.only语法去执行,:

// index.test.js

test.only("这个测试会被执行", () => {
  expect("A").toBe("A");
});

test("这个测试会被跳过", () => {
  expect("B").toBe("B");
});

这样就可以单独运行一个测试用例,其它测试会被跳过

5.9 Jest 中的 Mock

1. 函数的 Mock

在我们的项目中,一个模块的方法内通常会去调用另外一个模块的方法。

在测试时,我们可能并不需要关心方法内部的执行过程和结果,只想知道它是否被正确调用即可,此时,就需要用到Jest的使用Mock函数了。

Mock 函数提供的以下三种特性,在我们写测试代码时十分有用:

  • 捕获函数调用情况
  • 设置函数返回值
  • 改变函数的内部实现

1.1 测试函数是否被正常调用

首先定义一个函数,用来执行传入的回调,然后导出:

// index.js

export const runCallback = callback => {
  callback();
};

然后我们需要这样测试:

// index.test.js

import { runCallback } from "./index";

test("测试 runCallback", () => {
  const func = jest.fn(); // 生成 mock 函数,捕获函数的调用
  runCallback(func); // 调用 mock 函数
  expect(func).toBeCalled(); // toBeCalled 匹配器用来检查函数是否被调用过
});

1.2 测试函数调用次数是否正确

首先定义一个函数,用来执行传入的回调,然后导出:

// index.js

export const runCallback = callback => {
  callback();
};

然后我们需要这样测试:

// index.test.js

import { runCallback } from "./index";

test("测试调用次数", () => {
  const func = jest.fn(); // 生成 mock 函数,捕获函数的调用
  runCallback(func); // 第一次调用 mock 函数
  runCallback(func); // 第二次调用 mock 函数
  runCallback(func); // 第三次调用 mock 函数
  expect(func.mock.calls.length).toBe(3); // 检查函数是否被调用了三次
});

1.3 测试函数是否返回 undefined

首先定义一个函数,用来执行传入的回调,然后导出:

// index.js

export const runCallback = callback => {
  callback();
};

然后我们需要这样测试:

// index.test.js

import { runCallback } from "./index";

test("测试返回值", () => {
 const func = jest.fn(); // 生成 mock 函数,捕获函数的调用
  expect(runCallback(func)).toBeUndefined(); // 检查函数是否返回 undefined
});

六、章节总结

这篇文章讲了jest相关的一些基础内容,如有错误,敬请指正。

大家可以参阅官方文档进行学习jest,在学习这部分基础内容之后,你的jest就算入门了。

后面如果有时间,我还会出一篇jest高级用法的文章,比如snapshot 快照测试mock timersES6中类的测试对 DOM 节点的测试