react生态下jest单元测试

举报
建帅小伙儿 发表于 2022/09/25 03:21:52 2022/09/25
【摘要】 一:jest框架搭建 1.在本地创建一个目录jest_practice 2.使用编辑器VScode打开目录,紧接着在终端中打开,执行npm init 3.执行以下命令: 注意:这里我们使用cnpm去安装速度会更快,npm速度会很慢! a.建议使用npm install –g jest(不需要单个去安装依赖),...

一:jest框架搭建

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

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

image-1650460118214

3.执行以下命令:

注意:这里我们使用cnpm去安装速度会更快,npm速度会很慢!
a.建议使用npm install –g jest(不需要单个去安装依赖),修改package.json文件即可。

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


   
  1. cnpm install --save-dev jest babel-jest babel-core babel-preset-env regenerator-runtime
  2. cnpm i --save-dev @babel/plugin-transform-runtime
  3. cnpm i --save-dev @babel/preset-env
  4. cnpm install @babel/runtime

image-1650460136860

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

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

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

image-1650460173217

如上图说明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


   
  1. {
  2. "reporters": [
  3. "default",
  4. ["./node_modules/jest-html-reporters", {
  5. "publicPath": "./test/html-reports",
  6. "filename": "report.htm",
  7. "pageTitle": "Test Report",
  8. "expand": true
  9. }]
  10. ]
  11. }

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

完整报告:image-1650460200972

报错详情:image-1650460209425

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


   
  1. constructor() {
  2. this.init();
  3. }
  4. init() {
  5. this.a = 1;
  6. this.b = 1;
  7. }
  8. sum() {
  9. return this.a + this.b;
  10. }
  11. }
  12. module.exports = Hook;

文件名:Hook.test.js


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

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

image-1650460268621

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

image-1650460273870

2.SnapShot Testing(快照测试):

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

更新快照命令:jest --updateSnapshot

被测组件代码如下:


   
  1. //被测组件
  2. import React from 'react';
  3. const STATUS = {
  4. HOVERED: 'hovered',
  5. NORMAL: 'normal',
  6. };
  7. export default class Link extends React.Component {
  8. constructor() {
  9. super();
  10. this.state = {
  11. class: STATUS.NORMAL,
  12. };
  13. }
  14. _onMouseEnter = () => {
  15. this.setState({class: STATUS.HOVERED});
  16. };
  17. _onMouseLeave = () => {
  18. this.setState({class: STATUS.NORMAL});
  19. };
  20. render() {
  21. return (
  22. <a
  23. className={this.state.class}
  24. href={this.props.page || '#'}
  25. onMouseEnter={this._onMouseEnter}
  26. onMouseLeave={this._onMouseLeave}
  27. >
  28. {this.props.children}
  29. </a>
  30. );
  31. }
  32. }

快照测试case:


   
  1. import React from "react";
  2. import Link from "./Link.react";
  3. import renderer from "react-test-renderer";// react-test-renderer则负责将组件输出成 JSON 对象以方便我们遍历、断言或是进行 snapshot 测试
  4. //React 组件的 render 结果是一个组件树,并且整个树最终会被解析成一个纯粹由 HTML 元素构成的树形结构
  5. it("renders correctly", () => {
  6. const tree = renderer
  7. .create(<Link page="http://www.instagram.com">Instagram</Link>)
  8. .toJSON();
  9. expect(tree).toMatchSnapshot();
  10. });
  11. it("renders as an anchor when no page is set", () => {
  12. const tree = renderer.create(<Link>Facebook</Link>).toJSON();
  13. expect(tree).toMatchSnapshot();
  14. });
  15. it("properly escapes quotes", () => {
  16. const tree = renderer
  17. .create(<Link>{"\"Facebook\" \\'is \\ 'awesome'"}</Link>)
  18. .toJSON();
  19. expect(tree).toMatchSnapshot();
  20. });
  21. it("changes the class when hovered", () => {
  22. const component = renderer.create(
  23. <Link page="http://www.facebook.com">Facebook</Link>
  24. );
  25. let tree = component.toJSON();
  26. expect(tree).toMatchSnapshot();
  27. // manually trigger the callback
  28. tree.props.onMouseEnter();
  29. // re-rendering
  30. tree = component.toJSON();
  31. expect(tree).toMatchSnapshot();
  32. // manually trigger the callback
  33. tree.props.onMouseLeave();
  34. // re-rendering
  35. tree = component.toJSON();
  36. expect(tree).toMatchSnapshot();
  37. });
  38. it("renders correctly", () => {
  39. const tree = renderer
  40. .create(<Link page="https://prettier.io">Prettier</Link>)
  41. .toJSON();
  42. expect(tree).toMatchInlineSnapshot(`
  43. <a
  44. className="normal"
  45. href="https://prettier.io"
  46. onMouseEnter={[Function]}
  47. onMouseLeave={[Function]}
  48. >
  49. Prettier
  50. </a>
  51. `);
  52. });
  53. //1.通常,在对象中有一些字段需要快照,这些字段是生成的(比如id和Dates)。如果尝试对这些对象进行快照,它们将强制快照在每次运行时失败.
  54. //2.Jest允许为任何属性提供非对称匹配器。在写入或测试快照之前,将检查这些匹配器,然后将其保存到快照文件而不是接收到的值
  55. it('will check the matchers and pass', () => {
  56. const user = {
  57. createdAt: new Date(),
  58. id: Math.floor(Math.random() * 20),
  59. name: 'LeBron James',
  60. };
  61. expect(user).toMatchSnapshot({
  62. createdAt: expect.any(Date),
  63. id: expect.any(Number),
  64. });
  65. });

//1.通常,在对象中有一些字段需要快照,这些字段是生成的(比如id和Dates)。如果尝试对这些对象进行快照,它们将强制快照在每次运行时失败.//2.Jest允许为任何属性提供非对称匹配器。在写入或测试快照之前,将检查这些匹配器,然后将其保存到快照文件而不是接收到的值


   
  1. it('will check the matchers and pass', () => {
  2. const user = {
  3. createdAt: new Date(),
  4. id: Math.floor(Math.random() * 20),
  5. name: 'LeBron James',
  6. };
  7. expect(user).toMatchSnapshot({
  8. createdAt: expect.any(Date),
  9. id: expect.any(Number),
  10. });
  11. });

生成快照文件:

image-1650460351260

image-1650460359600

快照文件:


   
  1. // Jest Snapshot v1, https://goo.gl/fbAQLP
  2. exports[`changes the class when hovered 1`] = `
  3. <a
  4. className="normal"
  5. href="http://www.facebook.com"
  6. onMouseEnter={[Function]}
  7. onMouseLeave={[Function]}
  8. >
  9. Facebook
  10. </a>
  11. `;
  12. exports[`changes the class when hovered 2`] = `
  13. <a
  14. className="hovered"
  15. href="http://www.facebook.com"
  16. onMouseEnter={[Function]}
  17. onMouseLeave={[Function]}
  18. >
  19. Facebook
  20. </a>
  21. `;
  22. exports[`changes the class when hovered 3`] = `
  23. <a
  24. className="normal"
  25. href="http://www.facebook.com"
  26. onMouseEnter={[Function]}
  27. onMouseLeave={[Function]}
  28. >
  29. Facebook
  30. </a>
  31. `;
  32. exports[`properly escapes quotes 1`] = `
  33. <a
  34. className="normal"
  35. href="#"
  36. onMouseEnter={[Function]}
  37. onMouseLeave={[Function]}
  38. >
  39. "Facebook" \\'is \\ 'awesome'
  40. </a>
  41. `;
  42. exports[`renders as an anchor when no page is set 1`] = `
  43. <a
  44. className="normal"
  45. href="#"
  46. onMouseEnter={[Function]}
  47. onMouseLeave={[Function]}
  48. >
  49. Facebook
  50. </a>
  51. `;
  52. exports[`renders correctly 1`] = `
  53. <a
  54. className="normal"
  55. href="http://www.instagram.com"
  56. onMouseEnter={[Function]}
  57. onMouseLeave={[Function]}
  58. >
  59. Instagram
  60. </a>
  61. `;
  62. exports[`will check the matchers and pass 1`] = `
  63. Object {
  64. "createdAt": Any<Date>,
  65. "id": Any<Number>,
  66. "name": "LeBron James",
  67. }
  68. `;

更新快照命令:jest --updateSnapshot

3.组件测试

组件代码:example 1


   
  1. 'user strict';
  2. function timeGame(callback){
  3. console.log('ready...go!!!');
  4. setTimeout(() => {
  5. console.log('Time is up ,please stop!!!');
  6. callback && callback();
  7. }, 1000);
  8. }
  9. module.exports = timeGame;
  10. //export default timeGame;

module.exports = timeGame;
//export default timeGame;

组件测试代码:


   
  1. import { jest } from '@jest/globals';
  2. import ReactTestUtils from 'react-dom/test-utils';
  3. 'user strict';
  4. jest.useFakeTimers();
  5. describe('时间计时器', () => {
  6. test('wait 1 second before ending the game', () => {
  7. const timeGame = require('./timeGame');
  8. timeGame();
  9. expect(setTimeout).toHaveBeenCalledTimes(1);
  10. expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000)
  11. });
  12. test('calls the callback after 1 second ', () => {
  13. const timeGame = require('./timeGame');
  14. const callback = jest.fn();
  15. timeGame(callback);
  16. expect(callback).not.toBeCalled();
  17. jest.runAllTimers();
  18. expect(callback).toBeCalled();
  19. expect(callback).toHaveBeenCalledTimes(1);
  20. });
  21. });

example2:

组件代码:


   
  1. import React from "react";
  2. class Button extends React.Component {
  3. constructor() {
  4. super();
  5. this.state = {
  6. disabled: false,
  7. };
  8. this.handClick = this.handClick.bind(this);
  9. }
  10. handClick() {
  11. if (this.state.disabled) {
  12. return;
  13. }
  14. if (this.props.onClick) {
  15. this.props.onClick();
  16. }
  17. this.setState({ disabled: true });
  18. setTimeout(() => {
  19. this.setState({ disabled: false });
  20. }, 200);
  21. }
  22. render() {
  23. return (
  24. <button className="my-button" onClick={this.handClick}>
  25. {this.props.children}
  26. </button>
  27. );
  28. }
  29. }
  30. export default Button;

组件测试代码:


   
  1. import React from 'react';
  2. //import TestRenderer from 'react-test-renderer';调用 TestRenderer 的 create 方法并传入要 render 的组件就可以获得一个 TestRenderer 的实例
  3. import { jest } from '@jest/globals';
  4. import ReactTestUtils from 'react-dom/test-utils';
  5. import Button from './Button.jsx';
  6. describe('组件测试', () => {
  7. it('测试应该被回调', () => {
  8. const onClickMock = jest.fn();
  9. const testInstance = ReactTestUtils.renderIntoDocument(
  10. <Button onClick={onClickMock}>hello</Button>
  11. );
  12. const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
  13. ReactTestUtils.Simulate.click(buttonDom);
  14. expect(onClickMock).toHaveBeenCalled();
  15. });
  16. it('点击之后2秒之内是否可用', () => {
  17. const testInstance = ReactTestUtils.renderIntoDocument(<Button>hello</Button>);
  18. const buttonDom = ReactTestUtils.findRenderedDOMComponentWithClass(testInstance, 'my-button');
  19. ReactTestUtils.Simulate.click(buttonDom);
  20. expect(testInstance.state.disabled).toBeTruthy();
  21. jest.useFakeTimers();
  22. jest.advanceTimersByTime(100);
  23. expect(testInstance.state.disabled).toBeTruthy();
  24. jest.advanceTimersByTime(201);
  25. expect(testInstance.state.disabled).toBeTruthy();
  26. });
  27. });

4.函数测试


   
  1. //函数:去除空格
  2. function removeSpace(s) {
  3. if (s == undefined || s == "") {
  4. return "";
  5. } else {
  6. return s.replace(/(^\s*)|(\s*$)/g, "");
  7. }
  8. }
  9. export default removeSpace;

函数测试:


   
  1. import removeSpace from './removeSpace';
  2. //带空格的字符串
  3. test('Removal of space', function () {
  4. const string = ' camelot.china ';
  5. return expect(removeSpace(string)).toBe('camelot.china');
  6. });
  7. //字符串string = undefined
  8. test('string == undifined', () => {
  9. const string = undefined;
  10. return expect(removeSpace(string)).toBe("")
  11. });
  12. //字符串string = ""
  13. test('string = ""', () => {
  14. const string = "";
  15. return expect(removeSpace(string)).toBe("")
  16. });

image-1650460505677

image-1650460511831

5.mock测试


   
  1. //mock_fuction.js
  2. export default {
  3. sum(a , b){
  4. return a + b
  5. }
  6. };

   
  1. import React from 'react';
  2. import mock_function from './mock_fuction';
  3. import { jest } from '@jest/globals';
  4. import { object } from 'prop-types';
  5. //mock_fuction.test.js
  6. test('测试jest.fn()', () => {
  7. let mockFn = jest.fn();
  8. let result = mockFn(1, 2, 3);
  9. expect(result).toBeUndefined();
  10. expect(mockFn).toHaveBeenCalledWith(1, 2, 3);
  11. expect(mockFn).toBeCalled();
  12. expect(mockFn).toBeCalledTimes(1);
  13. });
  14. test('sum(5, 5) ', () => {
  15. expect(mock_function.sum(5, 5)).toBe(10);
  16. });
  17. test('测试jest.fn()返回固定值', () => {
  18. let mockFn = jest.fn().mockResolvedValue('default');
  19. expect(mockFn).toBe('default');
  20. });
  21. test('测试jest.fn()内部实现', () => {
  22. let mockFn = jest.fn((num1, num2) => {
  23. return num1 * num2;
  24. });
  25. expect(mockFn(9, 9)).toBe(100);
  26. });
  27. test('测试jest.fn()返回promise', async () => {
  28. let mockFn = jest.fn().mockResolvedValue('default');
  29. let result = await mockFn();
  30. expect(result).toBe('default');
  31. expect(Object.prototype.toString.call(mockFn())).toBe("[object Promise]")
  32. });

文章来源: blog.csdn.net,作者:懿曲折扇情,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq_41332844/article/details/126837418

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。