Skip to content

Jest 单测学习笔记 #40

@Lmagic16

Description

@Lmagic16

目录:

Jest 篇:

  1. 简介
  2. 匹配器(Matchers)
  3. 测试异步代码
  4. 安装和移除(hook 辅助函数)
  5. Mock 函数
  6. 快照测试(Snapshot Testing)
  7. 其他

Enzyme 篇:

  1. 简介

  2. Shallow Rendering

  3. Full DOM Rendering

  4. Static Rendered Markup

Jest 篇

一、简介

官网:https://jestjs.io/zh-Hans/

Jest 是一个令人愉快的 JavaScript 测试框架,专注于简洁明快。他适用但不局限于使用以下技术的项目:Babel, TypeScript, Node, React, Angular, Vue

特性:零配置、支持快照、隔离的、API 简洁方便、可并行运行测试、Mock 方便。

为了让加速测试进程,Jest 会先运行先前失败的测试,并根据测试文件需要多长时间重新组织测试。

通过添加--coverage标志生成代码覆盖率报告,无需额外设置。

当测试报错时,Jest 会提供丰富的上下文内容。

二、匹配器(Matchers)

完整匹配器列表:https://jestjs.io/docs/zh-Hans/expect

1. 普通匹配器

  • toBe 精确匹配器,使用 Object.is 来测试精确相等。

  • toEqual 递归检查对象或数组的每个字段。

  • 可以通过 .not.toBe测试相反的匹配。

2. Truthiness

  • toBeNull 只匹配 null
  • toBeUndefined 只匹配 undefined
  • toBeDefinedtoBeUndefined 相反
  • toBeTruthy 匹配任何 if 语句为真
  • toBeFalsy 匹配任何 if 语句为假

3. 数字

  • toBeGreaterThan
  • toBeGreaterThanOrEqual
  • toBeLessThan
  • toBeLessThanOrEqual
  • toBeCloseTo 比较浮点数是否相等

4. 字符串

  • toMatch 用于检查具有正则表达式的字符串

5. Arrays and iterables

  • toContain 用于检查一个数组或可迭代对象是否包含某个特定项

6. 其他

  • toThrow 用于测试特定函数是否抛出了一个错误

三、测试异步代码

1. 回调

test('the data is peanut butter', done => {
  function callback(data) {
    try {
      expect(data).toBe('peanut butter');
      done();
    } catch (error) {
      done(error);
    }
  }

  fetchData(callback);
});

Jest 会等 done 回调函数执行结束后,结束测试。若 expect 执行失败,它会抛出一个错误,后面的 done() 不再执行。 若我们想知道测试用例为何失败,我们必须将 expect 放入 try 中,将 error 传递给 catch 中的 done函数。 否则,最后控制台将显示一个超时错误失败,不能显示我们在 expect(data) 中接收的值。

2. Promises

注意需要把 promise 作为返回值。

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

使用 .catch 可以测试 rejected 状态。需要添加 expect.assertions 来验证一定数量的断言被调用。 否则一个 fulfilled 态的 Promise 不会让测试失败。

test('the fetch fails with an error', () => {
  expect.assertions(1);
  return fetchData().catch(e => expect(e).toMatch('error'));
  // return expect(fetchData()).resolves.toBe('peanut butter'); 
  // 也可以使用 resolves 或者 rejects
});

3. Async/Await

test('the data is peanut butter', async () => {
  const data = await fetchData();
  expect(data).toBe('peanut butter');
  // await expect(fetchData()).resolves.toBe('peanut butter');
  // await expect(fetchData()).rejects.toThrow('error');
});

test('the fetch fails with an error', async () => {
  expect.assertions(1);
  try {
    await fetchData();
  } catch (e) {
    expect(e).toMatch('error');
  }
});

四、安装和移除(hook 辅助函数)

1. 多次测试重复设置

beforeEach 和 afterEach,为多次测试重复设置的工作

beforeEach(() => {
  initializeCityDatabase();
});

afterEach(() => {
  clearCityDatabase();
});

test('city database has Vienna', () => {
  expect(isCity('Vienna')).toBeTruthy();
});

test('city database has San Juan', () => {
  expect(isCity('San Juan')).toBeTruthy();
});

2. 一次性设置

beforeAll 和 afterAll,在文件的开头做一次设置。

3. 作用域和执行顺序

默认情况下,beforeafter 的块可以应用到文件中的每个测试。 此外可以通过 describe 块来将测试分组。 当 beforeafter 的块在 describe 块内部时,则其只适用于该 describe 块内的测试。

执行顺序:

beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('Scoped / Nested block', () => {
  beforeAll(() => console.log('2 - beforeAll'));
  afterAll(() => console.log('2 - afterAll'));
  beforeEach(() => console.log('2 - beforeEach'));
  afterEach(() => console.log('2 - afterEach'));
  test('', () => console.log('2 - test'));
});

// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll

describe 和 test 块的执行顺序:

describe('outer', () => {
  console.log('describe outer-a');

  describe('describe inner 1', () => {
    console.log('describe inner 1');
    test('test 1', () => {
      console.log('test for describe inner 1');
      expect(true).toEqual(true);
    });
  });

  console.log('describe outer-b');

  test('test 1', () => {
    console.log('test for describe outer');
    expect(true).toEqual(true);
  });

  describe('describe inner 2', () => {
    console.log('describe inner 2');
    test('test for describe inner 2', () => {
      console.log('test for describe inner 2');
      expect(false).toEqual(false);
    });
  });

  console.log('describe outer-c');
});

// describe outer-a
// describe inner 1
// describe outer-b
// describe inner 2
// describe outer-c
// test for describe inner 1
// test for describe outer
// test for describe inner 2

五、Mock 函数

1. 函数

通过jest.fn来模拟函数。

所有的 mock 函数都有这个特殊的 .mock属性,它保存了关于此函数如何被调用、调用时的返回值的信息。 .mock 属性还追踪每次调用时 this的值,所以我们同样可以也检视(inspect) this

const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback); //需要测试的函数是forEach

// 此 mock 函数被调用了两次
expect(mockCallback.mock.calls.length).toBe(2);

// 第一次调用函数时的第一个参数是 0
expect(mockCallback.mock.calls[0][0]).toBe(0);

// 第二次调用函数时的第一个参数是 1
expect(mockCallback.mock.calls[1][0]).toBe(1);

// 第一次函数调用的返回值是 42
expect(mockCallback.mock.results[0].value).toBe(42);

2. 模块

通过 jest.mock('axios')可以模拟 axios 模块,通过.mockResolvedValue方法可以模拟模块方法的返回值。

3. 自定义匹配器

toBeCalled、toBeCalledWith、lastCalledWith 其实就是检查 .mock 属性的语法糖。

// The mock function was called at least once
expect(mockFunc).toBeCalled();

// The mock function was called at least once with the specified args
expect(mockFunc).toBeCalledWith(arg1, arg2);

// The last call to the mock function was called with the specified args
expect(mockFunc).lastCalledWith(arg1, arg2);

六、快照测试(Snapshot Testing)

每当你想要确保你的UI不会有意外的改变,快照测试是非常有用的工具。

import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.facebook.com">Facebook</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});

七、其他

1. Jest Platform

独立的包:

  • jest-changed-files,用于识别 git/hg 中已修改文件的工具。

  • jest-diff

  • jest-docblock,用于提取和解析 JavaScript 文件顶部注释的工具

  • jest-get-type,获取 js 值的原始类型

  • jest-validate,用于验证用户提交的配置的工具。

  • jest-worker,用于任务并行化的模块。

  • pretty-format

2. 计时器模拟

使用jest.useFakeTimers()可以模拟 setTimeout。

使用 jest.runAllTimers()可以“快进”时间使得所有定时器回调被执行。

在需要测试有循环定时的函数时可使用jest.runOnlyPendingTimers()来代替 jest.runAllTimers,否则将陷入无限循环。

此外,还有jest.clearAllTimersjest.advanceTimersByTime函数。

3. ES6 Class Mock

有 4 种方式可以模拟 ES6 模块。

import SoundPlayer from './sound-player';
jest.mock('./sound-player'); // 可以自动模拟模块 SoundPlayer

使用 mockImplementation 或者 mockImplementationOnce

Enzyme 篇

一、简介

官网:https://enzymejs.github.io/enzyme/

Enzyme 是用于 React 的 JS 测试工具。可以方便地测试 React 组件的输出,它模拟了 jQuery 的 API,DOM 操作和遍历非常灵活易用。

可以搭配如下工具使用:

二、Shallow Rendering

shallow 浅渲染:从 V3 版本开始,shallow 会调用 React 生命周期的方法。shallow 会将一个组件渲染为虚拟的 DOM 对象,不会渲染其子组件。

根据单测的纯净性原则,对组件的测试尽量都用 shallow。

shallow(node[, options]) => ShallowWrapper

ShallowWrapper API:https://enzymejs.github.io/enzyme/docs/api/shallow.html

.find(selector) => ShallowWrapper // 根据选择器查找节点
.findWhere(predicate) => ShallowWrapper
.filter(selector) => ShallowWrapper
.filterWhere(predicate) => ShallowWrapper
.hostNodes() => ShallowWrapper
.contains(nodeOrNodes) => Boolean
.containsMatchingElement(node) => Boolean
.containsAllMatchingElements(nodes) => Boolean
.containsAnyMatchingElements(nodes) => Boolean
.equals(node) => Boolean
.matchesElement(node) => Boolean
.hasClass(className) => Boolean
.is(selector) => Boolean
.exists([selector]) => Boolean
.isEmpty() => Boolean
.isEmptyRender() => Boolean
.not(selector) => ShallowWrapper
.children([selector]) => ShallowWrapper
.childAt(index) => ShallowWrapper
.parents([selector]) => ShallowWrapper
.parent() => ShallowWrapper
.closest(selector) => ShallowWrapper
.shallow([options]) => ShallowWrapper
.render() => CheerioWrapper
.setContext(context) => ShallowWrapper
.state([key]) => Any
.unmount() => Self
.debug([options]) => String // 返回渲染的类 HTML 字符串,用于调试
.simulate(event[, ...args]) => Self // 模拟事件触发
.text() => String // 返回当前组件的文本内容
.props() => Object // 返回根组件的所有属性
.state([key]) => Any // 返回根组件的状态
.setState(nextState[, callback]) => Self // 设置组件的 state,谨慎使用
.setProps(nextProps[, callback]) => Self // 设置根组件的 props

三、Full DOM Rendering

mount 完全渲染:会对组件进行完整渲染,包括其子组件,所有的 DOM API 都可用。mount 依赖于 jsdom 库,本质上是用 JS 实现的无头浏览器。

mount(node[, options]) => ReactWrapper

Full Rendering API:https://enzymejs.github.io/enzyme/docs/api/mount.html

四、Static Rendered Markup

render 静态渲染:使用第 3 方 解析和遍历库 Cheerio 渲染成 HTML,并可以分析 HTML 的结构,

import { render } from 'enzyme';

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions