jest 是 facebook 推出的一款测试框架。
JEST 官方文档:https://jestjs.io/docs/en/getting-started 需要连接外网。
开始安装:
yarn add --dev jest
or npm:
npm install --save-dev jest
提示:Jest 支持 yarn 的命令,但是 npm 也能工作。你可以比较 yarn 和 npm 在 yarn 命令文档里https://yarnpkg.com/en/docs/migrating-from-npm#toc-cli-commands-comparison
测试用例文件名格式为**test.js(默认配置下)
分组(Test Group):descripe(描述语,function)
测试用例(Test Case):test(描述语,function)
断言(Assert):expect(运行需测试的方法并返回实际结果).toBe(预期结果)
例如:
Pencil.query = (name, url) => {
//方法,返回捕获
// ?hello=test&wonghan=handsome
var reg = new RegExp("(?:\\?|&)" + name + "=(.*?)(?:&|$)");
var ret = reg.exec(url) || [];
return ret[1];
};
test("query", () => {
// testCase
// 断言
expect(Pencil.query("hello", "?hello=test")).toBe("test");
expect(Pencil.query("hello", "?hello2=test")).toBe(undefined);
//可以写多个`ecpect()`
});
test("query2", () => {
expect(Pencil.query("hello/test", "?hello/test=test")).toBe("test");
});
//可以写多个`test()`
describe("test query", () => {
test("query3", () => {
// testCase
// assert
expect(Pencil.query("hello", "?hello=test")).toBe("test");
expect(Pencil.query("hello", "?hello2=test")).toBe(undefined);
});
});
1、package.json
在 package.json 添加配置项“jest”:{配置项}
2、jest.config.js
新建 jest.config.js 并添加配置项 module.exports = { 配置项 }
配置项:
(1). testMatch
设置识别哪些文件是测试文件(glob 形式),与 testRegex 互斥,不能同时写
testMatch: ["**/__tests__/**/*.js?(x)", "**/?(*.)(spec|test).js?(x)"];
(2). testRegex
设置识别哪些文件是测试文件(正则形式),与 testMatch 互斥,不能同时写
testRegex: "(/__tests__).*|(\\\\.|/)(test|spec))\\\\.jsx?$";
(3). testRnviroment
测试环境,默认值是:jsdom,可修改为 node
testEnvironment: "jsdom";
4、rootDir
默认值:当前目录,一般是 package.json 所在的目录。
rootDir: " ";
5、moduleFileExtensions
测试文件的类型
moduleFileExtensions: ["js", "json", "jsx", "node"];
一般配置:
module.exports = {
testMatch: [’/test/**/*.js’],
testEnvironment: ‘jsdom’,
rootDir: ‘’,
moduleFileExtensions: [‘js’,‘json’,‘jsx’,‘node’]
}
下面让我们开始写一个两个数字相加的简单逻辑,创建一个 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
{
"script":{
"test":"jest"
}
}
最后,运行 yarn test or npm run test and Jest will print this message:
PASS ./sum.test.js
adds 1+2 to equals 3(5ms)
这个测试使用 expect 和 toBe 来测试两个值是否完全相同。
要了解 Jest 可以测试的其他东西,请参见使用匹配器:https://jestjs.io/docs/en/using-matchers
从命令行运行
您可以直接从 CLI 运行 Jest(如果它在您的路径中是全局可用的,例如通过 yarn global 添加 jest 或 npm 安装 jest-global),并提供各种有用的选项。
下面介绍如何使用配置在匹配 my-test 的文件上运行 Jest。配置文件,并在运行后显示一个本机 OS 通知:
jest my-test --notify --config=config.json
如果你想要学习更多关于 jest 命令行的知识,可以参考 Jest CLI OPTION https://jestjs.io/docs/cli
Jest 使用“匹配器”让您以不同的方式测试值。本文将介绍一些常用的匹配器。有关完整列表,请参阅 expect API 文档。
常见的匹配器
测试一个值最简单的方法是使用完全相等的方法。
test("two plus two is four", () => {
expect(2 + 2).toBe(4);
});
在这段代码中,expect(2 + 2)返回一个“期望”对象。通常,除了在这些期望对象上调用匹配器外,您不会对它们做太多的工作。在这个代码中,. tobe(4)是匹配器。当 Jest 运行时,它会跟踪所有失败的匹配器,以便为您打印出漂亮的错误消息。
toBe 使用对象,是为了检验确切的等式。如果你想检查一个对象的值,用 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);
}
}
});
在测试中,有时需要区分未定义、null 和 false,但有时不希望以不同的方式对待它们。Jest 包含一些帮助程序,可以让您明确地表达您想要的内容。
举例:
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 and toEqual are equivalent for numbers
expect(value).toBe(4);
expect(value).toEqual(4);
});
对于浮点等式,使用 toBeCloseTo 而不是 toEqual,因为您不希望测试依赖于微小的舍入误差。
test("adding floating point numbers", () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); This won't work because of rounding error
expect(value).toBeCloseTo(0.3); // This works.
});
你可以检查字符串正则表达式与 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/);
});
数组和可迭代的对象。
你可以检查一个数组或 iterable 是否包含一个特定的项目使用 tocontains:
const shoppingList = [
"diapers",
"kleenex",
"trash bags",
"paper towels",
"beer",
];
test("the shopping list has beer on it", () => {
expect(shoppingList).toContain("beer");
expect(new Set(shoppingList)).toContain("beer");
});
如果要测试某个特定函数在调用时抛出错误,请使用 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/);
});
这只是一个尝试。要获得完整的匹配器列表,请查看参考文档。
一旦您了解了可用的匹配器,下一步就是检查 Jest 如何让您测试异步代码。
代码异步运行在 JavaScript 中很常见。当您有异步运行的代码时,Jest 需要知道它正在测试的代码何时完成,然后才能转移到另一个测试。Jest 有几种处理方法。
最常见的异步模式是回调。
例如,假设您有一个 fetchData(回调)函数,它获取一些数据并在完成时调用回调(数据)。您想要测试这个返回的数据是否是字符串“peanut butter”(花生酱)。
默认情况下,Jest 测试在执行结束后才会完成。这意味着该测试将无法正常工作:
// Don't do this!
test("the data is peanut butter", () => {
function callback(data) {
expect(data).toBe("peanut butter");
}
fetchData(callback);
});
问题是,一旦 fetchData 完成,测试就会在调用回调之前完成。
有一种替代形式的测试可以解决这个问题。不要将测试放在一个带有空参数的函数中,而是使用一个名为 done 的参数。Jest 将在完成测试之前一直等到 done 回调被调用。
test("the data is peanut butter", done => {
function callback(data) {
expect(data).toBe("peanut butter");
done();
}
fetchData(callback);
});
如果从来不调用 done(),测试就会失败,这是您希望发生的情况。
使用一个名为 done 的参数,Jest 会一直等待 done 回调的执行,一旦 done 回调执行完毕,测试即完成。如果 done 一直没有被回调,那么测试失败。
如果使用的是 promise,测试将更加简单。只需要在测试中返回一个 promise,Jest 会自动等待 promise 被解析处理,如果 promise 被拒绝,那么测试失败。
expect.assertions(1);
test("the data is peanut butter", () => {
expect.assertions(1);
return fetchData().then(data => {
expect(data).toBe("peanut butter");
});
});
test("the fetch fails with an error", () => {
expect.assertions(1);
return fetchData().catch(e => {
expect(e).toMatch("error");
});
});
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) 确保两个回调都实际被调用。
注意:确保返回 promise,如果忽略掉 return,那么测试会在 fetchData 完成之前完成。
如果您希望 Promises 被拒绝,请使用.catch 方法。确保添加 expect。断言,以验证调用了一定数量的断言。否则,兑现的承诺就不会失败。
test("the fetch fails with an error", () => {
return fetchData().catch(e => expect(e).toMatch("error"));
});
.resolves / .rejects
您还可以在 expect 语句中使用.resolves matcher, Jest 将等待该 promise 被解析。如果 promise 被拒绝,测试将自动失败。
test("the data is peanut butter", () => {
return expect(fetchData()).resolves.toBe("peanut butter");
});
test("the fetch fails with an error", () => {
return expect(fetchData()).rejects.toMatch("error");
});
同样,您也可以在测试中使用 async 和 await 关键字。想要编写异步测试,只需要在传递给 test 的函数前加上 async 关键字。
例如,可以使用以下方法测试相同的 fetchData 场景:
test("the data is peanut butter", async () => {
const data = await fetchData();
expect(data).toBe("peanut butter");
});
test("the fetch fails with an error", async () => {
try {
await fetchData();
} catch (e) {
expect(e).toMatch("error");
}
});
也可以结合 async 和 await、.resolves、.rejects 使用:
test("the data is peanut butter", async () => {
await expect(fetchData()).resolves.toBe("peanut butter");
});
test("the fetch fails with an error", async () => {
await expect(fetchData()).rejects.toThrow("error");
});
在这些情况下,异步和等待是与承诺示例使用的逻辑相同的有效语法糖。
通常在编写测试时,您需要在测试运行前进行一些设置工作,并在测试运行后进行一些完成工作。Jest 提供了帮助函数来处理这个问题
如果您有一些工作需要在许多测试中重复执行,那么您可以使用 beforeEach 和 afterEach
例如,假设几个测试与一个城市数据库交互。您有一个方法 initializeCityDatabase(),它必须在每个测试之前调用,还有一个方法 clearCityDatabase(),它必须在每个测试之后调用。你可以这样做:
beforeEach(() => {
initializeCityDatabase();
});
afterEach(() => {
clearCityDatabase();
});
test("city database has Vienna", () => {
expect(isCity("Vienna")).toBeTruthy();
});
test("city database has San Juan", () => {
expect(isCity("San Juan")).toBeTruthy();
});
所谓 snapshot,即快照也。通常涉及 UI 的自动化测试,思路是把某一时刻的标准状态拍个快照,在测试回归的时候进行 pixel to pixel 的对比。但 Jest 对 React 组件的快照则不同,其实是把一个组件给序列化成纯文本, 纯文本的比较,这个真是简单又高效呀。对于一个 React 组件而言, 传入相同的 props,我们是期望得到相同的输出, 这样子一来,通过构造不同的 props, 我们即有了不同的测试用例。理想状态中,组件若是无内部状态变化,测试用例覆盖率应该可以达到 100%了。当然,仅仅是理想。
先跑跑官网的简单例子,先照步骤安装 npm 依赖,然后敲代码,jest 跑一下:
第一次跑的时候,就会生成一个快照文件,在snapshots目录下:
在之后的 toMatchSnapshot()调用就会与之比较,如有不同,则是用例失败,会打印出具体差异:
按 F 键只运行失败的测试
按 O 键只运行与更改文件相关的测试,修改配置直接进入 O 模式
按 P 键按文件名 regex 模式进行筛选
按 T 键按测试名称 regex 模式进行筛选
按 Q 键监测退出模式
按 Enter 键触发测试模式
%stmts 是语句覆盖率(statement coverage):是不是每个语句都执行了?
%Branch 分支覆盖率(branch coverage):是不是每个 if 代码块都执行了?
%Funcs 函数覆盖率(function coverage):是不是每个函数都调用了?
%Lines 行覆盖率(line coverage):是不是每一行都执行了?
函数写完了,那怎么测试呢,测试代码放到什么地方呢?Jest 识别三种测试文件,以.test.js 结尾的文件,以.spec.js 结尾的文件,和放到tests 文件夹中的文件。Jest 在进行测试的时候,它会在整个项目进行查找,只要碰到这三种文件它都会执行。干脆,再写两个函数,用三种测试文件分别进行测试, func.js 如下:
function greeting(guest) {
return `Hello ${guest}`;
}
function createObj(name, age) {
return {
name,
age,
};
}
function isTrueOrFasle(bool) {
return bool;
}
module.exports = {
greeting,
createObj,
isTrueOrFasle,
};
新建 greeting.test.js 测试 greeting 函数,createObj.spec.js 来测试 createObj 函数,新建一个tests 文件夹,在里面建一个 isTrue.js 来测试 isTrueOrFalse 函数。 具体到测试代码的书写,jest 也有多种方式,可以直接在测试文件中写一个个的 test 或 it 用来测试,也可以使用 describe 函数,创建一个测试集,再在 describe 里面写 test 或 it , 在 jest 中,it 和 test 是一样的功能,它们都接受两个参数,第一个是字符串,对这个测试进行描述,需要什么条件,达到什么效果。第二个是函数,函数体就是真正的测试代码,jest 要执行的代码。来写一下 greeting.test.js 文件,greeting 函数的作用就是 传入 guest 参数,返回 Hello guest. 那对应的一个测试用例就是 传入 sam,返回 Hello sam. 那描述就可以这么写, should return Hello sam when call greeting with param sam, 具体到测试代码,引入 greeting 函数,调用 greeting 函数,传入‘sam’ 参数, 作一个断言,函数调用的返回值是不是等于 Hello sam. greeting.test.js 如下
const greeting = require("./fun").greeting;
test("should return Hello sam when input sam", () => {
let result = greeting("sam");
expect(result).toBe("Hello sam");
});
测试的写法为三步,引入测试内容,运行测试内容,最后做一个断言进行比较,是否达到预期。Jest 中的断言使用 expect, 它接受一个参数,就是运行测试内容的结果,返回一个对象,这个对象来调用匹配器(toBe) ,匹配器的参数就是我们的预期结果,这样就可以对结果和预期进行对比了,也就可以判断对不对了。按照 greeting 测试的写法,再写一下 createObj 的测试,使用 it
const createObj = require("./fun").createObj;
it('should return {name: "sam", age: 30} when input "sam" and 30', () => {
let result = createObj("sam", 30);
expect(result).toEqual({ name: "sam", age: 30 }); // 使用toEqual
});
最后是 isTrueOrFalse 函数的测试,这里最好用 describe(). 因为这个测试分为两种情况,一个 it 或 test 搞不定。对一个功能进行测试,但它分为多种情况,需要多个 test, 最好使用 descibe() 把多个 test 包起来,形成一组测试。只有这一组都测试完成之后,才能说明这个功能是好的。它的语法和 test 的一致,第一个参数也是字符串,对这一组测试进行描述, 第二个参数是一个函数,函数体就是一个个的 test 测试。
const isTrueOrFasle = require("../fun").isTrueOrFasle;
describe("true or false", () => {
it("should return true when input true", () => {
let result = isTrueOrFasle(true);
expect(result).toBeTruthy(); // toBeTruthy 匹配器
});
test("should return false when input fasle", () => {
let result = isTrueOrFasle(false);
expect(result).toBeFalsy(); // toBeFalsy 匹配器
});
});
三个测试写完了,那就运行一下,看看对不对。把 package.json 中的 scripts 的 test 字段的值改成 ‘jest’, 然后 npm run test 进行测试, 可以看到三个测试都通过了。 修改一下,让一个测试不通过,比如 isTrue.js 中把第一个改成 false,
it("should return true when input true", () => {
let result = isTrueOrFasle(false);
expect(result).toBeTruthy(); // toBeTruthy 匹配器
});
再运行 npm run test 或者 yarn test
可以看到失败了,也指出了失败的地方,再看一下它的描述,它把组测试放到前面,后面是一个测试用例的描述,这样,我们就很轻松看到哪一个功能出问题了,并且是哪一个 case. 这也是把同一个功能的多个 test case 放到一起的好处。
我们再把它改回去,再执行 npm run test,如果这样改动测试,每一次都要执行测试的时候,使用 npm run test 就有点麻烦了,jest 提供了一个 watchAll 参数,会对测试文件以及测试文件引用的源文件进行实时监听,如果有变化,立即进行测试。package.json 中的 test 改成成 jest --watchAll
"scripts": {
"test": "jest --watchAll"
}
npm run test, 就可以启动 jest 的实时测试了。当然你也可以随时停止掉,按 q 键就可以。
1.相等断言
toBe(value): 比较数字、字符串
toEqual(value): 比较对象、数组
toBeNull()
toBeUndefined()
2.包含断言
toHaveProperty(keyPath, value): 是否有对应的属性
toContain(item): 是否包含对应的值,括号里写上数组、字符串
toMatch(regexpOrString): 括号里写上正则
3.逻辑断言
toBeTruthy()
toBeFalsy()
在 JavaScript 中,有六个 falsy 值:false,0,’’,null, undefined,和 NaN。其他一切都是 Truthy。
toBeGreaterThan(number): 大于
toBeLessThan(number): 小于
4.not
取反,用法见下面例子
test("matchers", () => {
const a = {
hello: "jest",
hi: {
name: "jest",
},
};
const b = {
hello: "jest",
hi: {
name: "jest",
},
};
// 以下结果均为true
expect(a).toEqual(b);
expect([1, 2, 3]).toEqual([1, 2, 3]);
expect(null).toBeNull();
expect([1, 2, 3]).toContain(1);
expect(b).toHaveProperty("hi");
expect("123").toContain("2");
expect("123").toMatch(/^\d+$/);
expect("123").not.toContain("4");
});
https://www.bookstack.cn/read/jest-v24.1/introduction.md
https://jestjs.io/docs/en/setup-teardown
https://www.cnblogs.com/SamWeb/p/11454923.html