Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jest 零基础入门 #16

Open
li-jia-nan opened this issue Oct 15, 2022 · 0 comments
Open

Jest 零基础入门 #16

li-jia-nan opened this issue Oct 15, 2022 · 0 comments

Comments

@li-jia-nan
Copy link
Owner

一、引言

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

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

二、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 节点的测试

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant