Jest 使用语法
Jest 是当下流行的 JavaScript 测试工具。 由Facebook 出品,不仅可用来测试 JavaScript 代码,还包括 React 应用。有以下优点:
-
零配置
自动测试 test 文件夹或 .spec.js .test.js 结尾的文件
-
速度快、沙盒化
使用 workers 并行化测试提高性能,console.log 的输出可以和测试结果一并打印,沙盒化测试,两次测试不会出现影响冲突。
-
代码覆盖报告
jest –coverage 自动输出测试报告
开始
npm install --save-dev jest
package.json 中增加测试命令
{
"scripts": {
"test": "jest"
}
}
// sum.js
export default function(a, b) {
return a + b;
}
// sum.test.js
import sum from './sum'
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
匹配器 Matchers
Jest 使用 matchers 来测试各种各样的值是否满足预期。一个测试用例函数test 接收连个参数,用例的描述(string 类型)与一个函数,函数内部包含匹配语句。
如测试值
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
测试对象
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
主要的 Matcher 种类:
Truthiness
- .toBeNull() 只匹配 null
- .toBeUndefined() 只匹配 undefined
- .toBeDefined() toBeUndefined 的取反
- .toBeTruthy() 匹配是否等同 if 语句的真
- .toBeFalsy() 匹配是否等同 if 语句的假
每个断言对应的有 not 修饰, 如 expect(n).not.toBeNull()
Number 数值比较
- .toBeGreaterThan(3)
- .toBeGreaterThanOrEqual(3.5)
- .toBeLessThan(5)
- .toBeLessThanOrEqual(4.5)
小数比较,由于JS 精度问题使用 .toBeCloseTo()
String
使用 toMatch 匹配是否包含某正则表达式
- .toMatch(/re/)
- .not.toMatch(/re/)
Array
是否包含某些元素
expect([1, 2, 3]).toContain(3) // passed
异常
function compileAndroidCode() {
throw new ConfigError('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(compileAndroidCode).toThrow();
expect(compileAndroidCode).toThrow(ConfigError);
// 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 运行至代码尾部即结束,此时无法等到异步的回调函数被调用,因此 test 的箭头函数需加上参数done函数
test('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter');
done();
}
fetchData(callback);
});
Promise
Jest 将等待 Promise 被 resolve,reject 如果没被捕获,test 将返回fail
test('the data is peanut butter', () => {
expect.assertions(1);
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
在 test 内需将 Promise 对象返回,否则测试会在Promise返回前结束。
expect.assertions(1) 声明断言被调用的次数,由于 Promise 被 resolve test 既通过,因此需使用 assertions 确保断言被调用
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});
// jest 20.0.0 版本以上增加了resolves 和 rejects 断言
test('the data is peanut butter', () => {
expect.assertions(1);
return expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', () => {
expect.assertions(1);
return expect(fetchData()).rejects.toMatch('error');
});
Async/Await
test 的函数参数使用 async 关键字修饰
test('the data is peanut butter', async () => {
expect.assertions(1);
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
await expect(fetchData()).rejects.toMatch('error');
});
预处理和后处理
- 多次设置 beforeEach AfterEach
- 一次设置 beforeAll afterAll
beforeEach(() => { // to do something })
测试块 describe 语句,包含一个块
describe('Scoped block', () => { beforeEach(() => console.log('2 - beforeEach')); afterEach(() => console.log('2 - afterEach')); test('', () => console.log('test')); });
Mock Functions
两种方式:
- 创建一个 mock function 用于测试
- 实现一个人工的 mock 覆盖原模块依赖
jest.fn() 返回一个mock 函数
const mockFunc = jest.fn();
mockFunc.mock.calls.length 函数被调用后,length 增加
mockFunc.mock.calls[0][0] 第一次调用的传入的第一个参数
mock 返回值及函数实现
- .mockReturnValueOnce 添加一次返回值
- .mockReturnValue 固定返回值
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock
.mockReturnValueOnce(10)
.mockReturnValueOnce('x')
.mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'
SnapShot Testing
测试 UI 是否发生了非预期变化的工具。抓取 React 树的快照或其它序列化的值来简化测试、分析监控状态变化。
Test renderer
react-test-renderer 包提供了一个 React 渲染器,在不依赖 DOM 或移动原生环境下,将 React 组件渲染为纯 JavaScript 对象。利用它很容易抓出组件的渲染快照,
import renderer from 'react-test-renderer';
({ name }) => <h1> Hello {name} </h1>;
const tree = renderer
.create(<Hello name="Snapshot testing" />)
.toJSON();
console.log(tree);
// { type: 'h1',
// props: {},
// children: [ ' Hello ', 'Snapshot testing', ' ' ] }
// 使用 toMatchSnapshot 匹配组件快照是否发生了变化
it('renders correctly', () => {
const tree = renderer
.create(<Hello name="Snapshot testing" />)
.toJSON();
expect(tree).toMatchSnapshot();
});
首次执行会生成一份快照文件,生成.snap 文件放于__snapshots__文件夹下,再次执行时会读取快照,进行比对。当组件视图更改时,需手动进行升级,使用命令 jest –updateSnapshot 更新快照,或使用 jest –watch 命令进行watch 交互模式,根据提示进行更新。
enzyme – DOM testing 利器
enzyme 是 Airbnb 发行的测试工具,很方便断言、模拟操作、遍历 React 组件的输出。enzyme 对组件内构成元素的选择、操纵与 jQuery 类似,可以选择元素比对内容、触发事件确认绑定函数是否被调用。
import React from "react";
import Enzyme, { shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });
function Counter({ value, onDecrement, onIncrement }) {
return (
<div>
<button onClick={onDecrement}>-</button>
<span>{value}</span>
<button onClick={onIncrement} />
</div>
);
}
function setup(value = 0) {
const actions = {
onIncrement: jest.fn(),
onDecrement: jest.fn()
};
const component = shallow(<Counter value={value} {...actions} />);
return {
component: component,
actions: actions,
buttons: component.find("button"),
p: component.find("span")
};
}
describe("Counter component", () => {
it("should display count", () => {
const { p } = setup();
expect(p.text()).toEqual("0");
});
it("first button should call onIncrement", () => {
const { buttons, actions } = setup();
buttons.at(0).simulate("click");
expect(actions.onDecrement).toBeCalled();
});
it("second button should call onDecrement", () => {
const { buttons, actions } = setup();
buttons.at(1).simulate("click");
expect(actions.onIncrement).toBeCalled();
});
});