react生态下jest单元测试

简介: react生态下jest单元测试

一:jest框架搭建


1.在本地创建一个目录jest_practice


2.使用编辑器VScode打开目录,紧接着在终端中打开,执行npm init

image.png

3.执行以下命令:


注意:这里我们使用cnpm去安装速度会更快,npm速度会很慢!

a.建议使用npm install –g jest(不需要单个去安装依赖),修改package.json文件即可。


b.安装jest框架,以及依赖

cnpm install --save-dev jest babel-jest babel-core babel-preset-env regenerator-runtime
cnpm i --save-dev @babel/plugin-transform-runtime
cnpm i --save-dev @babel/preset-env
cnpm install @babel/runtime

image.png

4.打开package.json文件,修改jest:

"test": "jest --config jest.config.json --no-cache --colors --coverage"

5.搭建好之后需要写个demo来测试是否正确

image.png

如上图说明jest框架搭建成功,进入编写case主题

%stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?

%Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?

%Funcs函数覆盖率(function coverage):是不是每个函数都调用了?

%Lines行覆盖率(line coverage):是不是每一行都执行了?


6.报告配置


需要在module层执行npm install jest-html-reporters --save-dev

新增jest.config.json


{
  "reporters": [
    "default",
    ["./node_modules/jest-html-reporters", {
      "publicPath": "./test/html-reports",
      "filename": "report.htm",
      "pageTitle": "Test Report",
      "expand": true
    }]
  ]
}

执行完case会在html-report目录下生成report.html报告


完整报告:

image.png

报错详情:

image.png

7.执行case方式:


三者都可以,需要安装yarn(cnpm install yarn)

1.npm test //执行全量test.js后缀的文件

2.yarn test --watchALL

3.jest Hook.test.js //执行单个case


二:开工须知


Jest背景:


Jest是 Facebook 发布的一个开源的、基于 Jasmine 框架的 JavaScript单元测试工具。提供了包括内置的测试环境DOM API支持、断言库、Mock库等,还包含了Spapshot Testing、 Instant Feedback等特性。


Enzyme:

React测试类库Enzyme提供了一套简洁强大的API,并通过jQuery风格的方式进行DOM处理,开发体验十分友好。不仅在开源社区有超高人气,同时也获得了React官方的推荐。


1.举例,被测函数:

文件名:Hook.js

  constructor() {
    this.init();
  }
  init() {
    this.a = 1;
    this.b = 1;
  }
  sum() {
    return this.a + this.b;
  }
}
module.exports = Hook;

文件名:Hook.test.js

describe('hook', () => {
  const hook = new Hook();
  // 每个测试用例执行前都会还原数据,所以下面两个测试可以通过。
  beforeEach(() => {
    hook.init();
  });
  test('test hook 1', () => {
    hook.a = 2;
    hook.b = 2;
    expect(hook.sum()).toBe(4);
  });
  test('test hook 2', () => {
    expect(hook.sum()).toBe(2);// 测试通过
  });
});

执行此目录下以test.js结尾的case :jest –colors –coverage 结果如下:

执行单个case:jest Hook.test.js –colors –coverage

image.png

会在html-report目录下生成report.html文件

image.png

2.SnapShot Testing(快照测试):


快照测试第一次运行的时候会将被测试ui组件在不同情况下的渲染结果保存一份快照文件。后面每次再运行快照测试时,都会和第一次的比较,若组件代码有所改变,则快照测试会失败,如果组件代码是最新的,优化过得代码,则需要更新快照,免得每次执行报错。


更新快照命令:jest --updateSnapshot


被测组件代码如下:

//被测组件
import React from 'react';
const STATUS = {
  HOVERED: 'hovered',
  NORMAL: 'normal',
};
export default class Link extends React.Component {
  constructor() {
    super();
    this.state = {
      class: STATUS.NORMAL,
    };
  }
  _onMouseEnter = () => {
    this.setState({class: STATUS.HOVERED});
  };
  _onMouseLeave = () => {
    this.setState({class: STATUS.NORMAL});
  };
  render() {
    return (
      <a
        className={this.state.class}
        href={this.props.page || '#'}
        onMouseEnter={this._onMouseEnter}
        onMouseLeave={this._onMouseLeave}
      >
        {this.props.children}
      </a>
    );
  }
}

快照测试case:

import React from "react";
import Link from "./Link.react";
import renderer from "react-test-renderer";// react-test-renderer则负责将组件输出成 JSON 对象以方便我们遍历、断言或是进行 snapshot 测试
//React 组件的 render 结果是一个组件树,并且整个树最终会被解析成一个纯粹由 HTML 元素构成的树形结构
it("renders correctly", () => {
  const tree = renderer
    .create(<Link page="http://www.instagram.com">Instagram</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});
it("renders as an anchor when no page is set", () => {
  const tree = renderer.create(<Link>Facebook</Link>).toJSON();
  expect(tree).toMatchSnapshot();
});
it("properly escapes quotes", () => {
  const tree = renderer
    .create(<Link>{"\"Facebook\" \\'is \\ 'awesome'"}</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});
it("changes the class when hovered", () => {
  const component = renderer.create(
    <Link page="http://www.facebook.com">Facebook</Link>
  );
  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();
  // manually trigger the callback
  tree.props.onMouseEnter();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
  // manually trigger the callback
  tree.props.onMouseLeave();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});
it("renders correctly", () => {
  const tree = renderer
    .create(<Link page="https://prettier.io">Prettier</Link>)
    .toJSON();
  expect(tree).toMatchInlineSnapshot(`
    <a
      className="normal"
      href="https://prettier.io"
      onMouseEnter={[Function]}
      onMouseLeave={[Function]}
    >
      Prettier
    </a>
  `);
});
//1.通常,在对象中有一些字段需要快照,这些字段是生成的(比如id和Dates)。如果尝试对这些对象进行快照,它们将强制快照在每次运行时失败.
//2.Jest允许为任何属性提供非对称匹配器。在写入或测试快照之前,将检查这些匹配器,然后将其保存到快照文件而不是接收到的值
it('will check the matchers and pass', () => {
  const user = {
    createdAt: new Date(),
    id: Math.floor(Math.random() * 20),
    name: 'LeBron James',
  };
  expect(user).toMatchSnapshot({
    createdAt: expect.any(Date),
    id: expect.any(Number),
  });
});

//1.通常,在对象中有一些字段需要快照,这些字段是生成的(比如id和Dates)。如果尝试对这些对象进行快照,它们将强制快照在每次运行时失败.

//2.Jest允许为任何属性提供非对称匹配器。在写入或测试快照之前,将检查这些匹配器,然后将其保存到快照文件而不是接收到的值

it('will check the matchers and pass', () => {
  const user = {
    createdAt: new Date(),
    id: Math.floor(Math.random() * 20),
    name: 'LeBron James',
  };
  expect(user).toMatchSnapshot({
    createdAt: expect.any(Date),
    id: expect.any(Number),
  });
});

生成快照文件:

image.png

image.png

快照文件:

// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`changes the class when hovered 1`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;
exports[`changes the class when hovered 2`] = `
<a
  className="hovered"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;
exports[`changes the class when hovered 3`] = `
<a
  className="normal"
  href="http://www.facebook.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;
exports[`properly escapes quotes 1`] = `
<a
  className="normal"
  href="#"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  "Facebook" \\'is \\ 'awesome'
</a>
`;
exports[`renders as an anchor when no page is set 1`] = `
<a
  className="normal"
  href="#"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Facebook
</a>
`;
exports[`renders correctly 1`] = `
<a
  className="normal"
  href="http://www.instagram.com"
  onMouseEnter={[Function]}
  onMouseLeave={[Function]}
>
  Instagram
</a>
`;
exports[`will check the matchers and pass 1`] = `
Object {
  "createdAt": Any<Date>,
  "id": Any<Number>,
  "name": "LeBron James",
}
`;

更新快照命令:jest --updateSnapshot


3.组件测试


组件代码:example 1

'user strict';
function timeGame(callback){
  console.log('ready...go!!!');
  setTimeout(() => {
    console.log('Time is up ,please stop!!!');
    callback && callback();
  }, 1000);
}
module.exports = timeGame;
//export default timeGame;

module.exports = timeGame;

//export default timeGame;


组件测试代码:

import { jest } from '@jest/globals';
import ReactTestUtils from 'react-dom/test-utils';
'user strict';
jest.useFakeTimers();
describe('时间计时器', () => {
  test('wait 1 second before ending the game', () => {
    const timeGame = require('./timeGame');
    timeGame();
    expect(setTimeout).toHaveBeenCalledTimes(1);
    expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000)
  });
  test('calls the callback after 1 second ', () => {
    const timeGame = require('./timeGame');
    const callback = jest.fn();
    timeGame(callback);
    expect(callback).not.toBeCalled();
    jest.runAllTimers();
    expect(callback).toBeCalled();
    expect(callback).toHaveBeenCalledTimes(1);
  });
});

example2:


组件代码:

import React from "react";
class Button extends React.Component {
  constructor() {
    super();
    this.state = {
      disabled: false,
    };
    this.handClick = this.handClick.bind(this);
  }
  handClick() {
    if (this.state.disabled) {
      return;
    }
    if (this.props.onClick) {
      this.props.onClick();
    }
    this.setState({ disabled: true });
    setTimeout(() => {
      this.setState({ disabled: false });
    }, 200);
  }
  render() {
    return (
      <button className="my-button" onClick={this.handClick}>
        {this.props.children}
      </button>
    );
  }
}
export default Button;

组件测试代码:

import React from 'react';
//import TestRenderer from 'react-test-renderer';调用 TestRenderer 的 create 方法并传入要 render 的组件就可以获得一个 TestRenderer 的实例
import { jest } from '@jest/globals';
import ReactTestUtils from 'react-dom/test-utils';
import Button from './Button.jsx';
describe('组件测试', () => {
  it('测试应该被回调', () => {
    const onClickMock = jest.fn();
    const testInstance = ReactTestUtils.renderIntoDocument(
      <Button onClick={onClickMock}>hello</Button>
    );
    const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
    ReactTestUtils.Simulate.click(buttonDom);
    expect(onClickMock).toHaveBeenCalled();
  });
  it('点击之后2秒之内是否可用', () => {
    const testInstance = ReactTestUtils.renderIntoDocument(<Button>hello</Button>);
    const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
    ReactTestUtils.Simulate.click(buttonDom);
    expect(testInstance.state.disabled).toBeTruthy();
    jest.useFakeTimers();
    jest.advanceTimersByTime(100);
    expect(testInstance.state.disabled).toBeTruthy();
    jest.advanceTimersByTime(201);
    expect(testInstance.state.disabled).toBeTruthy();
  });
});

4.函数测试

//函数:去除空格
function removeSpace(s) {
  if (s == undefined || s == "") {
      return "";
  } else {
      return s.replace(/(^\s*)|(\s*$)/g, "");
  }
}
export default removeSpace;

函数测试:

import removeSpace from './removeSpace';
//带空格的字符串
test('Removal of space', function () {
  const string = '  camelot.china  ';
  return expect(removeSpace(string)).toBe('camelot.china');
});
//字符串string = undefined
test('string == undifined', () => {
  const string = undefined;
  return expect(removeSpace(string)).toBe("")
});
//字符串string = ""
test('string = ""', () => {
  const string = "";
  return expect(removeSpace(string)).toBe("")
});

image.png

image.png

5.mock测试

//mock_fuction.js
export default {
  sum(a , b){
    return a + b
  }
};
import React from 'react';
import mock_function from './mock_fuction';
import { jest } from '@jest/globals';
import { object } from 'prop-types';
//mock_fuction.test.js
test('测试jest.fn()', () => {
  let mockFn = jest.fn();
  let result = mockFn(1, 2, 3);
  expect(result).toBeUndefined();
  expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
  expect(mockFn).toBeCalled();
  expect(mockFn).toBeCalledTimes(1);
});
test('sum(5, 5) ', () => {
  expect(mock_function.sum(5, 5)).toBe(10);
});
test('测试jest.fn()返回固定值', () => {
  let mockFn = jest.fn().mockResolvedValue('default');
  expect(mockFn).toBe('default');
});
test('测试jest.fn()内部实现', () => {
  let mockFn = jest.fn((num1, num2) => {
    return num1 * num2;
  });
  expect(mockFn(9, 9)).toBe(100);
});
test('测试jest.fn()返回promise', async () => {
  let mockFn = jest.fn().mockResolvedValue('default');
  let result = await mockFn();
  expect(result).toBe('default');
  expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]")
});


相关文章
|
7月前
|
资源调度 前端开发 JavaScript
React的测试:使用Jest和React Testing Library进行深入探索
【4月更文挑战第25天】本文探讨了使用Jest和React Testing Library进行React测试的方法。Jest是Facebook推出的JavaScript测试框架,适合React测试,提供全面的API和功能。React Testing Library侧重于组件行为,提倡按用户交互方式测试。安装这两个工具后,可通过编写测试用例(如模拟点击事件)来验证组件功能。运行Jest可执行测试并显示结果。此外,还介绍了高级测试技巧和模拟功能,强调了它们对于确保组件正确性、提升开发效率的重要性。
|
18天前
|
监控 JavaScript 前端开发
如何在实际应用中测试和比较React和Vue的性能?
总之,通过多种方法的综合运用,可以相对客观地比较 React 和 Vue 在实际应用中的性能表现,为项目的选择和优化提供有力的依据。
29 1
|
1月前
|
前端开发 JavaScript 安全
学习如何为 React 组件编写测试:
学习如何为 React 组件编写测试:
37 2
|
1月前
|
前端开发 JavaScript 测试技术
React 模拟测试与 Jest
【10月更文挑战第21天】本文介绍了如何使用 Jest 进行 React 组件的单元测试和模拟测试,涵盖了基础概念、常见问题及解决方案,并提供了实践案例。通过学习本文,你将掌握如何有效地使用 Jest 提高代码质量和稳定性。
49 1
|
1月前
|
资源调度 前端开发 JavaScript
React 测试库 React Testing Library
【10月更文挑战第22天】本文介绍了 React Testing Library 的基本概念和使用方法,包括安装、基本用法、常见问题及解决方法。通过代码案例详细解释了如何测试 React 组件,帮助开发者提高应用质量和稳定性。
47 0
|
2月前
|
前端开发 JavaScript 应用服务中间件
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
本文是一篇详细的教程,介绍了如何在Linux系统上安装和配置nginx,以及如何将打包好的前端项目(如Vue或React)上传和部署到服务器上,包括了常见的错误处理方法。
591 0
linux安装nginx和前端部署vue项目(实际测试react项目也可以)
|
4月前
|
前端开发 JavaScript 测试技术
React 与前端自动化测试也太重要啦!各种测试框架助力确保应用质量,快来开启优质开发之旅!
【8月更文挑战第31天】随着前端技术的发展,React 成为了构建用户界面的热门选择。然而,随着应用复杂性的增加,确保应用质量变得至关重要。本文介绍了前端自动化测试的重要性,并详细综述了常用的测试框架如 Jest、Enzyme 和 Cypress,以及如何通过它们进行高效的 React 组件测试。通过遵循最佳实践,如编写可维护的测试用例、覆盖关键场景、集成 CI/CD 流程和进行性能测试,可以显著提高应用的稳定性和可靠性。
90 0
|
4月前
|
前端开发 JavaScript 测试技术
[译] React 测试驱动开发:从用户故事到产品
[译] React 测试驱动开发:从用户故事到产品
|
6月前
|
前端开发 JavaScript 测试技术
Jest与React Testing Library:前端测试的最佳实践
Jest和React Testing Library是React应用测试的核心工具。安装相关依赖后,在`jest.config.js`中配置Jest。测试时,编写描述性测试用例,使用`render`、`fireEvent`和`screen`来检查组件行为。Jest提供模拟功能,如模拟API调用。测试组件交互性时,模拟用户行为并验证状态变化。确保覆盖边缘情况,使用代码覆盖率报告评估测试完整性,并将测试集成到CI流程中。
96 1
|
7月前
|
资源调度 前端开发 数据可视化
【第47期】一文了解React测试
【第47期】一文了解React测试
60 0