本文主要讲解如何基于 Vite 2 和 Vue 3 去构建一套比较完整的单元测试流程,希望看完后能够帮到你
📦 仓库地址:vue-unit 和 vue-unit-ts,欢迎 ✨ star ✨ ~~
安装及配置
1、使用创建 Vite Cli ,创建一个 vue3 空项目
yarn create vite
2、安装 jest
yarn add jest -D
3、安装完毕创建 jest.config.js 文件
// jest.config.js
module.exports = {
transform: {}
}
4、若想要使用 ES6 语法,则还需要安装 babel,执行下面的命令:
yarn add babel-jest @babel/core @babel/preset-env -D
5、配置 babel.config.js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
node: 'current'
}
}
]
],
};
5、配置 babel 到 jest.config.js
module.exports = {
transform: {
'^.+\\.js$': 'babel-jest'
}
}
6、因为我们主要测试 vue 组件,所以需要安装对应的 vue-jest 和 依赖包。
由于是 vue3 并且之前我安装的 jest 版本为 27.x,故执行命令(此处注意下自己安装的 jest 版本,不同版本的 jest,需要安装不同的 vue-jest)
可以参考:
yarn add @vue/vue3-jest@^27.0.0-alpha.3 @vue/test-utils@^2.0.0-rc.17 -D
7、安装完 vue-jest 后,需要进一步配置 jest.config.js
// jest.config.js
module.exports = {
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': '@vue/vue3-jest',
}
}
一般而言,单元测试代码都放到 __tests__ 文件夹中
我们可以显示的配置到 jest.config.js 中,像这样:
// jest.config.js
module.exports = {
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': '@vue/vue3-jest',
},
testMatch: ['<rootDir>/__tests__/**/*.spec.js']
}
之后我们写的每个单元测试文件都以 *.spec.js 结尾。
接下来我们通过一个例子来验证下效果,测试前别忘了在 package.json 中添加 jest 命令:
{
"scripts": {
"test": "jest"
}
}
例子
我们可以参考 vue-test-utils 官方 next.vue-test-utils.vuejs.org/guide/ 给的例子:
1、在 __tests__ 中创建一个 hello.spec.js 文件
// hello.spec.js
import { mount } from '@vue/test-utils'
// The component to test
const MessageComponent = {
template: '<p>{{ msg }}</p>',
props: ['msg']
}
test('displays message', () => {
const wrapper = mount(MessageComponent, {
props: {
msg: 'Hello world'
}
})
// Assert the rendered text of the component
expect(wrapper.text()).toContain('Hello world')
})
2、执行 yarn test 命令, 发现报错了,这个提示说明我们需要将单测环境配置为 jsdom 的方式
具体说明见官方文档: jestjs.io/docs/config…
yarn run v1.22.17
$ jest
FAIL __tests__/hello.spec.js
● displays message
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.
ReferenceError: document is not defined
9 |
10 | test('displays message', () => {
> 11 | const wrapper = mount(MessageComponent, {
| ^
12 | props: {
13 | msg: 'Hello world'
14 | }
at mount (node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:7757:14)
at Object.<anonymous> (__tests__/hello.spec.js:11:19)
我们把配置 testEnvironment: 'jsdom' 加上:
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': '@vue/vue3-jest',
},
testMatch: ['<rootDir>/__tests__/**/*.spec.js']
}
再次执行 yarn test ,PASS 成功了 🎉
3、可以发现,单测中的 vue 组件是临时创建出来的模板组件,并没有从 src 中引入,而实际的情况可能是:
1、在 src 下创建 .vue 组件
2、引入到 __tests__ 中,mount 挂载执行测试。
所以我们试着改下,不如就用默认的 App.vue 来做测试:
App.vue
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + Vite" />
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
将 App.vue 引入到 __tests__ 中:
先不着急执行单测,我们先来打印下 App ,看看能不能正确引入进来:
// hello.spec.js
import { mount } from '@vue/test-utils'
import App from '../src/App.vue'
console.log(App)
// The component to test
const MessageComponent = {
template: '<p>{{ msg }}</p>',
props: ['msg']
}
test('displays message', () => {
const wrapper = mount(MessageComponent, {
props: {
msg: 'Hello world'
}
})
// Assert the rendered text of the component
expect(wrapper.text()).toContain('Hello world')
})
执行 yarn test,不出意外,报错了:
● Test suite failed to run
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
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.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
Details:
/Users/wisdom/Documents/workspace/vue-unit/src/assets/logo.png:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){�PNG
SyntaxError: Invalid or unexpected token
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14)
at Object.<anonymous> (src/App.vue:40:36)
看下关键的描述信息即可,报错说明 logo.png 解析失败,其无法被转换,需要一个额外的 transform 去做处理
搜了下,发现 jest 默认并不会处理 assets 等资源文件,但有一个包可以解决这个问题: jest-transform-stub
安装下 jest-transform-stub :
yarn add jest-transform-stub -D
在 jest 配置文件中使用 jest-transform-stub
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': '@vue/vue3-jest',
'.+\\.(css|scss|png|jpg|svg)$': 'jest-transform-stub'
},
testMatch: ['<rootDir>/__tests__/**/*.spec.js']
}
重新执行一遍 yarn test,PASS 成功了 🎉
配置 alias
实际项目中,我们一般都会使用 alias 配置作为引入的绝对路径,如用 @ 符号来表示 src 目录,
那么在单测中怎么使用呢,是否需要做一些配置?
首先,我们不做任何处理试下,先把例子改下,为了方便起见,直接用 HelloWorld.vue 来测:
// hello.spec.js
import { mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
test('displays message', () => {
const wrapper = mount(HelloWorld, {
props: {
msg: 'Hello world'
}
})
// Assert the rendered text of the component
expect(wrapper.text()).toContain('Hello world')
})
猜测,直接 yarn test 会不会报错?
执行试下,果然不出所料,报错了:
FAIL __tests__/hello.spec.js
● Test suite failed to run
Cannot find module '@/components/HelloWorld.vue' from '__tests__/hello.spec.js'
结合刚刚提到的 @ alias 符号,需要稍微对 jest.config.js 进行下修改:
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1"
},
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': '@vue/vue3-jest',
'.+\\.(css|scss|png|jpg|svg)$': 'jest-transform-stub'
},
testMatch: ['<rootDir>/__tests__/**/*.spec.js']
}
moduleNameMapper参数,可用于将一个模块路径映射到另一个模块,见官方说明:
重新 yarn test,PASS 🎉
单元测试覆盖率
不知道是不是因为 jest 版本是 27.x 的原因,需要增加 coverageProvider: "v8" 参数才会生成覆盖率报告
module.exports = {
testEnvironment: 'jsdom',
moduleFileExtensions: ["js", "vue"],
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1"
},
testMatch: ['<rootDir>/__tests__/**/*.spec.js'],
transformIgnorePatterns: ['/node_modules/'],
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.(vue)$': '@vue/vue3-jest',
'.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub'
},
coverageDirectory: 'coverage',
coverageProvider: "v8",
collectCoverageFrom: [
'src/**/*.{js,vue}',
'!src/main.js',
'!src/App.vue'
],
coverageThreshold: {
global: {
branches: 40,
functions: 80,
lines: 90,
statements: 80
}
}
}
package.json 增加如下命令:
"test:coverage": "jest --coverage"
执行即可:
yarn run v1.22.17
$ jest --coverage
PASS __tests__/sum.spec.js
PASS __tests__/hello.spec.js
----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
HelloWorld.vue | 100 | 100 | 100 | 100 |
----------------|---------|----------|---------|---------|-------------------
Test Suites: 2 passed, 2 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 7.84 s
Ran all test suites.
✨ Done in 11.21s.
关联 Codecov
1、首次进入 app.codecov.io/gh/pdsuwwz/…,Codecov 应该会提示你授权 github,点击授权即可
2、授权完毕后可以选取对应关联哪个 github 项目,这里就选取 vue-unit
3、点击名称,进去后应该会提供给你一个 token,像这样:
点击 Copy 复制,随后我们进入 github 对应项目的密钥 secrets 设置页面:
创建一个新的 token,并命名为 CODECOV_TOKEN (名称随便起,这里起做 CODECOV_TOKEN 是因为随后方便在 github CI 中使用)
4、创建完 token 后,接着我们需要创建一个 github CI
首先在项目根目录创建 .github/workflows/unit.yml 文件
在文件内写入:
name: Unit-Testing
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12.20.x
- name: Install dependencies
run: yarn install
- name: Testing
run: yarn test:coverage
env:
CI: true
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
注意最后的 ${{ secrets.CODECOV_TOKEN }} ,github action 会在执行这个工作流的时候从secrets 中获取我们刚刚创建的 secrets token
5、显示 Codecov 小徽章
将代码推送到 github 上面,github 会自动执行工作流
工作流执行完毕后,我们可以在 README.md 中写入:
[](https://codecov.io/gh/pdsuwwz/vue-unit)
将 pdsuwwz 和 vue-unit 分别改成你自己的 用户名和仓库名就好了。
可以试着打开看下,应该长这样:
完事 ✿✿ヽ(°▽°)ノ✿
📦 仓库地址:vue-unit 和 vue-unit-ts,欢迎 ✨ star ✨ ~~