【我要做开源】给 vue devui 组件库项目增加单元测试

avatar
前端组件库 @华为

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

Vue DevUI是一个通过开源社区孵化的 Vue3 开源组件库,这意味着从一开始Vue DevUI就不是靠个人维护的项目,而是通过社区共同的力量进行持续演进的。

由于是新项目,在技术选型时,我们都是用的最新的技术:

  • Vite搭建基础工程和构建打包
  • 用最新的Vue3语法编写组件
  • TypeScript给代码增加类型系统
  • JSX语法编写Vue组件,确保最大的灵活性
  • VitePress搭建组件库的文档

刚开始我们只是搭了一个架子,写了一些组件,加了commit检查,工程化方面的东西还很不完善,没有单元测试、没有eslint、没有cli工具。

社区的小伙伴 Brenner 发现了这个问题,作为一个开源项目,怎么能没有单元测试呢?后面正式发布了,别人一看,单元测试都没有,还敢用吗?

所以Brenner同学在2021年6月13日,正式提交了一个PR,给Vue DevUI增加了基于JestVue Test Utils的单元测试环境。

Brenner 是我们的早期贡献者,vue devui2021.5.1正式在掘金招募contributor

让我们一起建设 Vue DevUI 项目吧!🥳

当时其实响应的人不算多,直到Brenner的出现,Brenner同学在6月13日悄悄地给vue devui提交了一个pr:

gitee.com/devui/vue-d…

之后几乎每隔一个星期都有提交pr,完善了单元测试、eslint等代码检查工具,并提交了好几个组件,比如:

  1. Radio
  2. Checkbox
  3. Switch
  4. TagInput
  5. Input

图片.png

Brenner同学给了我很大的信心,虽然现在Brenner同学已经退居幕后,但正是因为他的早期的持续贡献,让vue devui有个重大突破。

2个月之后的8月3日,vue devui的组件数量达到10个,我们在掘金同步了这个消息。

Vue DevUI 已经有10个组件成员啦~🥳😋

之后vue devui开始活跃,涌现了大量的田主和contributor。

让我们一起来看看,如何给Vue3组件库搭建单元测试环境,并给Vue组件增加单元测试。

1 引入 Jest

1.1 安装 Jest

yarn add -D jest @types/jest

1.2 增加脚本命令

package.json

"scripts": {
  "dev": "vite",
  "build": "vue-tsc --noEmit && vite build",
  "serve": "vite preview",
  "test": "jest" // 新增
},

1.3 编写测试用例

// Step 1: 定义一个测试套 Test Suite
describe('tree', () => {
  // Step 2: 定义一个单元测试 Test
  // i think 'tree should render correctly'
  it('tree should render correctly', () => {
    // Step 3: 期望(expect)tree组件的class里面包含(toContain)'devui-tree'
    expect(wrapper.classes()).toContain('devui-tree')
    
    // 期望(expect)tree组件的子元素数量为(toBe)6
    expect(wrapper.element.childElementCount).toBe(6)
  })
})

1.4 编写第一个单元测试

devui/tree/__tests__/tree.spec.ts

// Step 1: 定义一个测试套 Test Suite
describe('tree', () => {
  // Step 2: 定义一个单元测试 Test
  // i think 'tree should render correctly'
  it('should render correctly', () => {
    // Step 3: 编写测试断言,期望(expect)1等于1
    expect(1).toEqual(1)
  })
})

1.5 执行 test 命令

yarn test

图片.png

2 测试组件

2.1 安装@vue/test-utils

yarn add -D @vue/test-utils

2.2 编写组件测试代码

devui/tree/__tests__/tree.spec.ts

import { mount } from '@vue/test-utils'
import DTree from '../src/tree'

describe('tree', () => {
  it('should render correctly', () => {
    const wrapper = mount({
      components: { DTree },
      template: `
        <d-tree :data="data"></d-tree>
      `,
      setup() {
        const data = [{
          label: '一级 1', level: 1,
          children: [{
            label: '二级 1-1', level: 2,
            children: [{
              label: '三级 1-1-1', level: 3,
            }]
          }]
        }, {
          label: '一级 2', level: 1,
          open: true, // 新增
          children: [{
            label: '二级 2-1', level: 2,
            children: [{
              label: '三级 2-1-1', level: 3,
            }]
          }, {
            label: '二级 2-2', level: 2,
            children: [{
              label: '三级 2-2-1', level: 3,
            }]
          }]
        }, {
          label: '一级 3', level: 1,
          open: true, // 新增
          children: [{
            label: '二级 3-1', level: 2,
            children: [{
              label: '三级 3-1-1', level: 3,
            }]
          }, {
            label: '二级 3-2', level: 2,
            open: true, // 新增
            children: [{
              label: '三级 3-2-1', level: 3,
            }]
          }]
        }, {
          label: '一级 4', level: 1,
        }]

        return {
          data
        }
      }
    })

    expect(wrapper.classes()).toContain('devui-tree')
  })
})

再重新执行yarn test命令,发现报错啦~

3 遇到的问题及相应的解法

3.1 第一个报错:SyntaxError: Cannot use import statement outside a module

图片.png

这是一个比较典型的问题,jest解析文件过程中遇到的语法问题。

报错信息也提示了可能的原因和解法:

    Here's what you can do:If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

大意是你可能引入了ES6TypeScript,但是又没有配置相应的transform转换器。

安装 babel-jest

先安装依赖babel-jest@babel/preset-env

yarn add -D babel-jest @babel/preset-env

配置 jest transform

jest.config.js

module.exports = {
  transform: {
    '^.+\\.(ts|tsx|js|jsx)$': [
      'babel-jest', { presets: ['@babel/preset-env'] }
    ]
  },
};

再重新执行yarn test命令,发现又报错啦

3.2 第二个报错:Cannot find module 'vue-template-compiler'

图片.png

这个问题其实是没有安装正确的@vue/test-utils导致的,默认安装的是vue2版本的@vue/test-utils,但我们是vue3组件库,需要安装@vue/test-utils@next

我们先按照报错提示安装下vue-template-compiler试试看。

安装依赖 vue-template-compiler

yarn add -D vue-template-compiler

再重新执行yarn test命令,发现又双报错啦

3.3 第三个报错:Vue packages version mismatch

果然不是vue-template-compiler的问题,不过这个提示倒是提醒我们是版本不匹配的问题。

图片.png

安装Vue3版本的@vue/test-utils@next

yarn add -D @vue/test-utils@next

再重新执行yarn test命令,发现又双叒报错啦

接下来是一系列的语法错误SyntaxError,都是没有配置相应的transform转换器导致的。

3.4 第四个报错:SyntaxError: Unexpected token, expected ","

图片.png

安装 @babel/preset-typescript

yarn add -D @babel/preset-typescript

配置 @babel/preset-typescript

jest.config.js

module.exports = {
  transform: {
    '^.+\\.(ts|tsx|js|jsx)$': [
      'babel-jest', {
        presets: [
          '@babel/preset-env',
          '@babel/preset-typescript' // 新增
        ]
      }
    ]
  },
};

重新执行yarn test命令,还是报错~

3.5 第五个报错:SyntaxError: Unexpected token '<'

图片.png

安装 @vue/babel-plugin-jsx

yarn add -D @vue/babel-plugin-jsx

配置 @vue/babel-plugin-jsx

jest.config.js

module.exports = {
  transform: {
    '^.+\\.(ts|tsx|js|jsx)$': [
      'babel-jest', {
        presets: [
          '@babel/preset-env',
          '@babel/preset-typescript'
        ],
        plugins: ['@vue/babel-plugin-jsx'] // 新增
      }
    ]
  },
};

继续执行yarn test命令,还是报错~

3.6 第六个报错:SyntaxError: Invalid or unexpected token

图片.png

修改样式导入

import './tree.scss'

->

import './tree'

再重新执行yarn test命令,发现又双叒叕报错啦

3.7 第七个错:ReferenceError: document is not defined

图片.png

这个报错提示得很清楚:

The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string.
    Consider using the "jsdom" test environment.

测试环境错误,需要配置jsdom的测试环境。

修改测试环境 testEnvironment

jest.config.js

testEnvironment: 'jest-environment-jsdom',

终于成功了!图片.png

此处应该庆祝一下🎉!

图片.png

图片.png

小结

本文主要分享搭建vue3组件库单元测试环境的步骤、遇到的问题及相应的解法。

  1. 引入jest支持基本的单元测试
  2. 引入@vue/test-utils支持vue组件的单元测试
  3. 配置jest.config.js,增加@babel/preset-env@babel/preset-typescript两个preset以支持ES6TS语法,以及一个 @vue/babel-plugin-jsx plugin 以支持JSX语法
  4. 配置testEnvironmentjest-environment-jsdom
  5. 编写单元测试的三部曲:测试套单元测试测试断言
  6. 分析了搭建单元测试环境中遇到的典型问题及相应的解决方案

欢迎一起建设 DevUI 开源项目

我们 DevUI 团队有多个开源项目,现在都在招募contributor,欢迎大家一起参与开源中来!(感兴趣的小伙伴可以添加DevUI小助手的微信:devui-official,将你拉到我们的核心开发群)

DevUI官网:devui.design/

也欢迎关注我和村长的【Vue DevUI开源指南】系列直播!

Vue DevUI开源指南系列直播打算分成两条线:

  1. 组件设计和实现
  2. 组件库的工程化

目前【组件设计和实现】已经完成了3期(还未结束):

  1. 【我要做开源】华为大佬亲授,Vue DevUI开源指南01:提交我的第一次pr
  2. 【我要做开源】华为大佬亲授,Vue DevUI开源指南02:做一个有模有样的Tree组件
  3. 【我要做开源】华为大佬亲授,Vue DevUI开源指南03:学会“单测”才会有安全感!完成Tree组件!

【组件库工程化】已经完成了2期(正在进行中):

  1. 【我要做开源】华为大佬亲授,Vue DevUI开源指南04:组件库工程化建设之项目初始化、jsx支持
  2. 【我要做开源】华为大佬亲授,Vue DevUI开源指南05:开源组件库中的文档建设,vitepress使用过程中的踩坑经历,克服这些困难你将收获多多!
  3. 【我要做开源】Vue DevUI开源指南06:手把手带你开发一个脚手架

已经跟村长老师达成共识image.png,只要村长老师的直播间不倒,只要还有小伙伴愿意参与进来,这个系列就会一直做下去!

欢迎大家持续关注、分享出去~我们一起来从0到1做一个vue3开源组件库!

每周五晚上九点,我们在村长的直播间,不见不散!

村长直播间地址