jest

478 阅读5分钟
  • 单元测试特指被测试对象为程序中最小组成单元的测试。这里的最小组成单元可以是一个函数、一个类等等。
  • 很多单元测试框架都有 watch 模式。每次改动代码时都会自动执行单元测试
  • 频繁的执行意味着更早的发现问题。
  • 随着代码的不断迭代,程序中总有某些位置会频繁出现某类问题。在没有单元测试时程序员之间往往都是“口口相传”,隔一段时间很可能由于疏忽还会犯同一个错误。
  • 我们就可以为这些问题点编写对应的测试代码,每次提交代码前都执行一遍,可以极大的降低相同 bug 重复出现的概率。
  • 如果你面对一个函数、类却很难编写测试代码的时候,很可能是你的代码设计上存在问题。比如和外部依赖耦合过于紧密。这种情况下,编写单元测试的过程会倒逼我们优化我们代码的结构。将复杂的代码拆解成为更简单、更容易测试的片段。这个过程本身也会潜移默化的提高我们代码的质量。
  • 测试代码再简单,也是需要工作量来开发的

何时编写单元测试?

  • 开发过程中,单元测试应该来测试那些可能会出错的地方,或是那些边界情况。
  • 维护过程中,单元测试应该围绕着 bug 进行,每个 bug 都应该编写响应的单元测试。从而保证同一个 bug 不会出现第二次。

某个单元测试,往往包含以下几个步骤:

  • 准备阶段:构造参数,创建 spy 等
  • 执行阶段:用构造好的参数执行被测试代码
  • 断言阶段:用实际得到的结果与期望的结果比较,以判断该测试是否正常
  • 清理阶段:清理准备阶段对外部环境的影响,移除在准备阶段创建的 spy 等

易于测试的代码,说明是一个好的设计

  • 测试、功能开发相结合,有利于设计和代码重构
  • 集成了测试执行器、断言库、spy、mock、snapshot和测试覆盖率报告等功能

mock

function forEach(items, callback) {
  for (let index = 0; index < items.length; index++) {
    callback(items[index]);
  }
}
describe("forEach", () => {
  it("should call callback with each item", () => {
    const mockFn = jest.fn();
    forEach([1, 2], mockFn);

    expect(mockFn.mock.calls.length).toBe(2);
    expect(mockFn.mock.calls[0][0]).toBe(1);
    expect(mockFn.mock.calls[1][0]).toBe(2);
  });
});

spy

describe("bot", () => {
  it("should say hello", () => {
    const spy = jest.spyOn(bot, "sayHello");
    bot.sayHello("Michael");
    expect(spy).toHaveBeenCalledWith("Michael");
    spy.mockRestore();
  });
});

Jest是Facebook开发的一个测试框架

  • 它集成了测试执行器、断言库、spy、mock、snapshot和测试覆盖率报告等功能。React项目本身也是使用Jest进行单测的,因此契合度相当高。
  • Enzyme是由airbnb开发的React单元测试工具。它扩展了React的TestUtils并通过支持类似jQuery的find语法可以很方便的对render出来的结果做各种断言。
  • "断言",就是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。
  • 采用Jest+Enzyme实现前端代码的单元测试
"scripts": {
  "test": "jest --coverage", // 显示测试覆盖率
},
  • React 组件的单元测试中我们需要找到合适的方法对执行结果进行断言
  • 如何执行一个 React 组件并编写断言
  • react-test-renderer 则负责将组件输出成 JSON 对象以方便我们遍历、断言或是进行 snapshot 测试。
  • shallow render 比较适合验证组件输出的结构是否符合预期
  • 通过 .find() 方法查找了 className 为 my-link 的 a 标签并确保找到了 3 个。
describe('Button', () => {
  it('should be throttled to 200ms', () => {
    const wrapper = mount(<Button>hello</Button>);
    wrapper.find('.my-button').simulate('click');
    expect(wrapper.state('disabled')).toBeTruthy();
    jest.advanceTimersByTime(199);
    expect(wrapper.state('disabled')).toBeTruthy();
    jest.advanceTimersByTime(1);
    expect(wrapper.state('disabled')).toBeFalsy();
  });

  it('should call onClick callback if provided', () => {
    const onClickMock = jest.fn();
    const wrapper = mount(<Button onClick={onClickMock}>hello</Button>);
    wrapper.find('.my-button').simulate('click');
    expect(onClickMock).toHaveBeenCalled();
  });
});

enzyme中有几个比较核心的函数

  • simulate(event, mock):用来模拟事件触发,event为事件名称,mock为一个event object;
  • instance():返回测试组件的实例;
  • find(selector):根据选择器查找节点,selector可以是CSS中的选择器,也可以是组件的构造函数,以及组件的display name等;
  • at(index):返回一个渲染过的对象;
  • get(index):返回一个react node,要测试它,需要重新渲染;
  • contains(nodeOrNodes):当前对象是否包含参数重点 node,参数类型为react对象或对象数组;
  • text():返回当前组件的文本内容;
  • html(): 返回当前组件的HTML代码形式;
  • props():返回根组件的所有属性;
  • prop(key):返回根组件的指定属性;
  • state():返回根组件的状态;
  • setState(nextState):设置根组件的状态;
  • setProps(nextProps):设置根组件的属性;

浅渲染shallow

  • Shallow Rendering用于将一个组件渲染成虚拟DOM对象
  • 不需要DOM环境,因为根本没有加载进DOM
  • 只渲染第一层,处理速度非常快

完全渲染mount

  • 将React组件加载为真实DOM节点
  • 用jsdom模拟一个浏览器环境

静态渲染render

  • 将React组件渲染成静态的HTML字符串
  • 使用Cheerio这个库解析这段字符串,并返回一个Cheerio的实例对象,可以用来分析组件的html结构