{"id":17951793,"url":"https://github.com/superman66/react-test-demo","last_synced_at":"2025-03-25T00:31:35.754Z","repository":{"id":96365508,"uuid":"89314546","full_name":"superman66/react-test-demo","owner":"superman66","description":"React test demo with Jest and Enzyme","archived":false,"fork":false,"pushed_at":"2019-01-03T14:03:10.000Z","size":355,"stargazers_count":148,"open_issues_count":3,"forks_count":38,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-19T05:33:39.248Z","etag":null,"topics":["enzyme","jest","react","redux","unit-test"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/superman66.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-04-25T03:43:32.000Z","updated_at":"2023-09-25T12:01:44.000Z","dependencies_parsed_at":"2023-03-30T09:04:11.006Z","dependency_job_id":null,"html_url":"https://github.com/superman66/react-test-demo","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superman66%2Freact-test-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superman66%2Freact-test-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superman66%2Freact-test-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superman66%2Freact-test-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/superman66","download_url":"https://codeload.github.com/superman66/react-test-demo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245377920,"owners_count":20605374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["enzyme","jest","react","redux","unit-test"],"created_at":"2024-10-29T09:51:08.160Z","updated_at":"2025-03-25T00:31:35.713Z","avatar_url":"https://github.com/superman66.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 基于 Jest + Enzyme 的 React 单元测试\n* Jest 和 Enzyme 的基本介绍\n* 测试环境搭建\n* 测试脚本编写\n  * UI 组件测试\n  * Reducer 测试\n* 运行并调试\n* 参考资料\n\n## Jest、Enzyme 介绍\nJest 是 Facebook 发布的一个开源的、基于 `Jasmine` 框架的 JavaScript 单元测试工具。提供了包括内置的测试环境 DOM API 支持、断言库、Mock 库等，还包含了 Spapshot Testing、 Instant Feedback 等特性。\n\nAirbnb开源的 React 测试类库 Enzyme 提供了一套简洁强大的 API，并通过 jQuery 风格的方式进行DOM 处理，开发体验十分友好。不仅在开源社区有超高人气，同时也获得了React 官方的推荐。\n\n## 测试环境搭建\n在开发 React 应用的基础上（默认你用的是 Webpack + Babel 来打包构建应用），你需要安装 `Jest` `Enzyme`，以及对应的 `babel-jest`\n```\nnpm install jest enzyme babel-jest --save-dev\n```\n下载 npm 依赖包之后，你需要在 `package.json` 中新增属性，配置 Jest：\n\n```json\n  \"jest\": {\n    \"moduleFileExtensions\": [\n      \"js\",\n      \"jsx\"\n    ],\n    \"moduleNameMapper\": {\n      \"\\\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$\": \"\u003crootDir\u003e/__mocks__/fileMock.js\",\n      \".*\\\\.(css|less|scss)$\": \"\u003crootDir\u003e/__mocks__/styleMock.js\"\n    },\n    \"transform\": {\n      \"^.+\\\\.js$\": \"babel-jest\"\n    }\n  },\n```\n并新增`test scripts`\n```\n\"scripts\": {\n    \"dev\": \"NODE_ENV=development webpack-dev-server  --inline --progress --colors --port 3000 --host 0.0.0.0 \",\n    \"test\": \"jest\"\n  }\n```\n其中 :\n* `moduleFileExtensions`：代表支持加载的文件名，与 Webpack 中的 `resolve.extensions` 类似\n* `moduleNameMapper`：代表需要被 Mock 的资源名称。如果需要 Mock 静态资源（如less、scss等），则需要配置 Mock 的路径 `\u003crootDir\u003e/__mocks__/yourMock.js`\n* `transform` 用于编译 ES6/ES7 语法，需配合 `babel-jest` 使用\n\n上面三个是常用的配置，更多 Jest 配置见官方文档：[Jest Configuration](https://facebook.github.io/jest/docs/configuration.html)\n\n## 测试脚本编写\n### UI 组件测试\n环境搭建好了，就可以开始动手写测试脚本了。在开始之前，先分析下 Todo 应用的组成部分。\n\n![](https://github.com/superman66/react-test-demo/blob/master/screenshot/todo.png)\n\n应用主体结构如下 `src/component/App.js`：\n```javascript\nclass App extends Component {\n  render() {\n    const { params } = this.props;\n    return (\n      \u003csection className=\"todoapp\"\u003e\n        \u003cdiv className=\"main\"\u003e\n          \u003cAddTodo /\u003e\n          \u003cVisibleTodoList filter={params.filter || 'all'} /\u003e\n        \u003c/div\u003e\n         \u003cFooter /\u003e\n      \u003c/section\u003e\n    )\n  }\n}\n```\n\n可以发现 整个应用可以分为三个组件:\n* 最外层的 `\u003cApp /\u003e`\n* 中间的 Input 输入框 `\u003cAddTodo /\u003e`\n* 下面的 TODO 列表 `\u003cVisibleTodoList /\u003e`\n\n其中 `\u003cApp/\u003e` 是 UI 组件，`\u003cAddTodo /\u003e` 和 `\u003cVisibleTodoList /\u003e` 是智能组件，我们需要找到智能组件所对应的 UI 组件 `\u003cAddTodoView/\u003e` 和 `\u003cTodoList/\u003e`。\n\n\n`\u003cAddTodoView/\u003e` 就是一个 `Input` 输入框，接受文字输入，敲下回车键，创建一个 Todo。代码如下 `src/component/AddTodoView.js`：\n\n```javascript\nimport React, { Component, PropTypes } from 'react'\nclass AddTodoView extends Component {\n  render() {\n    return (\n      \u003cheader className=\"header\"\u003e\n        \u003ch1\u003etodos\u003c/h1\u003e\n        \u003cinput\n          className=\"new-todo\"\n          type=\"text\"\n          onKeyUp={e =\u003e this.handleClick(e)}\n          placeholder=\"input todo item\"\n          ref='input' /\u003e\n      \u003c/header\u003e\n    )\n  }\n\n  handleClick(e) {\n    if (e.keyCode === 13) {\n      const node = this.refs.input;\n      const text = node.value.trim();\n      text \u0026\u0026 this.props.onAddClick(text);\n      node.value = '';\n    }\n  }\n}\n```\n了解了该组件的功能之后，我们首先需要明确该组件需要测试哪些点：\n* 组件是否正常渲染\n* 当用户输入内容敲下回车键时，是否能正常的调用 `props` 传递的 `onAddClick(text)` 方法\n* 创建完成后清除 Input 的值\n* 当用户没有输入任何值时，敲下回车时，应该不调用 `props` 传递的 `onAddClick(text)` 方法\n\n经过上面的分析之后，我们就可以开始编写单元测试脚本了。\n\n#### 第一步：引入相关 lib\n\n```javascript\nimport React from 'react'\nimport App from '../../src/component/App'\nimport { shallow } from 'enzyme'\n```\n在这里我们引入了 `shallow` 方法，它是 `Enzyme` 提供的 API 之一，可以实现**浅渲染**。其作用是仅仅渲染至虚拟节点，不会返回真实的节点，能极大提高测试性能。但是它不适合测试包含子组件、需要测试声明周期的组件。\n`Enzyme` 还提供了其他两个 API：\n* `mount`：Full Rendering，非常适用于存在于 DOM API 存在交互组件，或者需要测试组件完整的声明周期\n* `render`：Static Rendering，用于 将 React 组件渲染成静态的 HTML 并分析生成的 HTML 结构。`render` 返回的 `wrapper` 与其他两个 API 类似。不同的是 `render` 使用了第三方 HTML 解析器和 `Cheerio`。\n\n一般情况下，`shallow` 就已经足够用了，偶尔情况下会用到 `mount`。\n\n#### 第二步：模拟 Props，渲染组件创建 Wrapper\n这一步，我们可以创建一个 `setup` 函数来实现。\n\n```javascript\nconst setup = () =\u003e {\n  // 模拟 props\n  const props = {\n    // Jest 提供的mock 函数\n    onAddClick: jest.fn()\n  }\n\n  // 通过 enzyme 提供的 shallow(浅渲染) 创建组件\n  const wrapper = shallow(\u003cAddTodoView {...props} /\u003e)\n  return {\n    props,\n    wrapper\n  }\n}\n```\n`Props` 中包含函数的时候，我们需要使用 Jest 提供的 [mockFunction](https://facebook.github.io/jest/docs/mock-function-api.html#content)\n\n#### 第四步：编写 Test Case\n这里的 Case 根据我们前面分析需要测试的点编写。\n\n**Case1：测试组件是否正常渲染**\n\n\n```javascript\ndescribe('AddTodoView', () =\u003e {\n  const { wrapper, props } = setup();\n\n  // case1\n  // 通过查找存在 Input,测试组件正常渲染\n  it('AddTodoView Component should be render', () =\u003e {\n    //.find(selector) 是 Enzyme shallow Rendering 提供的语法, 用于查找节点\n    // 详细用法见 Enzyme 文档 http://airbnb.io/enzyme/docs/api/shallow.html\n    expect(wrapper.find('input').exists());\n  })\n})\n```\n写完第一个测试用例之后，我们可以运行看看测试的效果。在 Terminal 中输入 `npm run test`,效果如下:\n\n![](https://github.com/superman66/react-test-demo/blob/master/screenshot/case1-test-result.png)\n\n**Case2: 输入内容并敲下回车键，测试组件调用props的方法**\n\n\n```javascript\n  it('When the Enter key was pressed, onAddClick() shoule be called', () =\u003e {\n    // mock input 输入和 Enter事件\n    const mockEvent = {\n      keyCode: 13, // enter 事件\n      target: {\n        value: 'Test'\n      }\n    }\n    // 通过 Enzyme 提供的 simulate api 模拟 DOM 事件\n    wrapper.find('input').simulate('keyup',mockEvent)\n    // 判断 props.onAddClick 是否被调用\n    expect(props.onAddClick).toBeCalled()\n  })\n```\n上面的代码与第一个 case 多了两点：\n* 增加了 `mockEvent`,用于模拟 DOM 事件\n* 使用 `Enzyme` 提供的 `.simulate(’keyup‘, mockEvent)` 来模拟点击事件,这里的 `keyup` 会自动转换成 React 组件中的 `onKeyUp` 并调用。\n\n我们再运行 `npm run test` 看看测试效果：\n\n![](https://github.com/superman66/react-test-demo/blob/master/screenshot/case12-test-result.png)\n\n经过上面两个 Test Case 的分析，接下来的 Case3 和 Case4 思路也是一样，具体写法见代码： [__test__/component/AddTodoView.spec.js](https://github.com/superman66/react-test-demo/blob/master/__test__/component/AddTodoView.spec.js)，这里就不一一讲解了。\n\n### Reducer 测试\n由于 Reducer 是纯函数，因此对 Reducer 的测试非常简单，Redux 官方文档也提供了测试的例子，代码如下：\n```javascript\nimport reducer from '../../reducers/todos'\nimport * as types from '../../constants/ActionTypes'\n\ndescribe('todos reducer', () =\u003e {\n  it('should return the initial state', () =\u003e {\n    expect(\n      reducer(undefined, {})\n    ).toEqual([\n      {\n        text: 'Use Redux',\n        completed: false,\n        id: 0\n      }\n    ])\n  })\n\n  it('should handle ADD_TODO', () =\u003e {\n    expect(\n      reducer([], {\n        type: types.ADD_TODO,\n        text: 'Run the tests'\n      })\n    ).toEqual(\n      [\n        {\n          text: 'Run the tests',\n          completed: false,\n          id: 0\n        }\n      ]\n    )\n\n    expect(\n      reducer(\n        [\n          {\n            text: 'Use Redux',\n            completed: false,\n            id: 0\n          }\n        ],\n        {\n          type: types.ADD_TODO,\n          text: 'Run the tests'\n        }\n      )\n    ).toEqual(\n      [\n        {\n          text: 'Run the tests',\n          completed: false,\n          id: 1\n        },\n        {\n          text: 'Use Redux',\n          completed: false,\n          id: 0\n        }\n      ]\n    )\n  })\n})\n\n```\n更多关于 Redux 的测试可以看官网提供的例子：[编写测试-Redux文档](http://cn.redux.js.org/docs/recipes/WritingTests.html)\n\n## 调试及测试覆盖率报告\n在运行测试脚本过程，`Jest` 的错误提示信息友好，通过错误信息一般都能找到问题的所在。\n同时 `Jest` 还提供了生成测试覆盖率报告的命令，只需要添加上 `--coverage` 这个参数既可生成。不仅会在终端中显示：\n\n![](https://github.com/superman66/react-test-demo/blob/master/screenshot/coverage-report.png)\n\n而且还会在项目中生成 `coverage` 文件夹，非常方便。\n# 资料\n* [聊一聊前端自动化测试](https://github.com/tmallfe/tmallfe.github.io/issues/37)\n* [Enzyme API](http://airbnb.io/enzyme/docs/api/index.html)\n* [Jest](https://facebook.github.io/jest/)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperman66%2Freact-test-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuperman66%2Freact-test-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperman66%2Freact-test-demo/lists"}