一. Jest 是什么

由 Facebook 开发的一套用于前端 JavaScript 的测试框架。Jest 的一个理念是提供一套完整集成的“零配置”测试体验。

二. 配置

NPM 包

*Vue-Cli3.0 内置了 Babel7,关于需要 Babel 包的这边就不提及了。

  • jest - npm i ``babel-jest jest
  • babel-jest -npm i babel-jest babel-jest自动编译 JavaScript 代码
  • @vue/test-utils - npm i @vue/test-utils官方提供了 Vue 组件单元测试的方法
  • vue-jest - npm i ``vue-jest 变压器和源映射支持

VUE 单文件组件支持

  • “@vue/cli-plugin-unit-jest”: “^3.11.0”,
  • “@vue/test-utils”: “1.0.0-beta.29”,
  • “babel-core”: “7.0.0-bridge.0”,

babel.config.js

我们假设 webpack 使用了 babel-preset-env,这时默认的 Babel 配置会关闭 ES modules 的转译,因为 webpack 已经可以处理 ES modules 了。然而,我们还是需要为我们的测试而开启它,因为 Jest 的测试用例会直接运行在 Node 上。 同样的,我们可以告诉 babel-preset-env 面向我们使用的 Node 版本。这样做会跳过转译不必要的特性使得测试启动更快。 为了仅在测试时应用这些选项,可以把它们放到一个独立的 env.test 配置项中 (这会被 babel-jest自动获取)。

  1. env: {
  2. test: {
  3. presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
  4. }
  5. }

jest.config.js

  1. module.exports = {
  2. /** 是否为浏览器环境 */
  3. browser: true,
  4. /** 模块支持的后缀名 */
  5. moduleFileExtensions: ['js', 'json', 'vue', 'jsx'],
  6. /** 解析文件配置 */
  7. transform: {
  8. '^.+\\.js$': 'babel-jest',
  9. '.*\\.(vue)$': 'vue-jest',
  10. '^.+\\.vue$': 'vue-jest',
  11. '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
  12. '^.+\\.jsx?$': 'babel-jest'
  13. },
  14. /** 模块别名设置 */
  15. moduleNameMapper: {
  16. '^@/(.*)$': '<rootDir>/src/$1',
  17. '\\.(css|less)$': 'identity-obj-proxy'
  18. },
  19. /** 测试文件匹配 */
  20. testMatch: [
  21. '**/__tests__/**/*.[jt]s?(x)',
  22. '**/?(*.)+(spec).[tj]s?(x)',
  23. '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
  24. ],
  25. /** 忽略文件 */
  26. transformIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/dist/', '/node_modules/'],
  27. /** 是否开启测试覆盖率统计 */
  28. collectCoverage: false,
  29. /** 测试覆盖率数据来源 */
  30. collectCoverageFrom: ['<rootDir>/src/utils/**/*.{js,vue}', '!**/node_modules/**'],
  31. /** 测试覆盖率报告默认格式 */
  32. coverageReporters: ['html', 'text-summary'],
  33. /** 测试覆盖率生成报告目录 */
  34. coverageDirectory: '<rootDir>/test/unit/coverage',
  35. /** 文件忽略的配置 */
  36. watchPathIgnorePatterns: ['<rootDir>/node_modules', '<rootDir>/dist', '<rootDir>/static'],
  37. /** 层次显示测试套件中每个测试的结果 */
  38. // bail: true
  39. verbose: true,
  40. /** 在遇到第一个失败后就停止继续运行测试用例 */ snapshotSerializers: ['jest-serializer-vue'],
  41. testURL: 'http://localhost/',
  42. watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname']
  43. }

package.json “scripts”

  1. "test": "jest",
  2. "test:watch": "jest --watch",
  3. "test:unit": "jest --coverage && npm run test:coverage",
  4. "test:coverage": "open test/unit/coverage/index.html"

例子🌰

utils

  1. import customFunc from '@/utils/customFunc'
  2. /** 数组索引项互换方法 */
  3. describe('customFunc.js', () => {
  4. test('数组指定索引项移动方法,源数组 [1, 2, 3, 4, 5] 将下标 0 的元素移动到下标 3,目标数组为 [2, 3, 4, 1, 5]', () => {
  5. const arrayBefore = [1, 2, 3, 4, 5]
  6. const arrayAfter = [2, 3, 4, 1, 5]
  7. const fromIndex = 0
  8. const toIndex = 3
  9. expect(customFunc.moveItem(arrayBefore, fromIndex, toIndex)).toStrictEqual(arrayAfter)
  10. })
  11. })

components

  1. import { createLocalVue, shallowMount } from '@vue/test-utils'
  2. import Vue from 'vue'
  3. import ElementUI from 'element-ui'
  4. import 'element-ui/lib/theme-chalk/index.css'
  5. import CommonBreadcrumb from '@/components/common/CommonBreadcrumb.vue'
  6. import { Breadcrumb, BreadcrumbItem } from 'element-ui'
  7. const localVue = createLocalVue()
  8. localVue.use(ElementUI)
  9. Vue.use(Breadcrumb)
  10. Vue.use(BreadcrumbItem)
  11. /** 挂载函数并返回已渲染的文本的工具函数 */
  12. function getRenderedText(propsData) {
  13. const wrapper = shallowMount(CommonBreadcrumb, {
  14. propsData: {
  15. fileBreadcrumb: propsData
  16. }
  17. })
  18. return wrapper.text()
  19. }
  20. function renderedTextIndexOfParams(args, renderedText) {
  21. return args.filter(ele => renderedText.indexOf(ele) === -1).length === 0
  22. }
  23. test('评估通用面包屑组件渲染文案', () => {
  24. // 默认文件夹
  25. expect(
  26. renderedTextIndexOfParams(
  27. ['默认文件夹'],
  28. getRenderedText([
  29. {
  30. pid: 0,
  31. label: '默认文件夹'
  32. }
  33. ])
  34. )
  35. ).toBe(true)
  36. // 默认文件夹/层级一
  37. expect(
  38. renderedTextIndexOfParams(
  39. ['默认文件夹', '层级一'],
  40. getRenderedText([
  41. {
  42. pid: 0,
  43. label: '默认文件夹'
  44. },
  45. {
  46. pid: 1,
  47. label: '层级一'
  48. }
  49. ])
  50. )
  51. ).toBe(true)
  52. })

参考