探索 vue-cli4 创建集成 mocha + chai 的项目中,如何写单元测试和查看单元测试覆盖率。
1、建立一个集成单元测试的项目
首先创建一个新的项目 vue-cli4-unit-mocha-chai-nyc:
vue create vue-cli4-unit-mocha-chai-nyc
# 选择手动配置
? Please pick a preset:
zcloud (router, vuex, less, babel, eslint, unit-jest)
default (babel, eslint)
❯ Manually select features
# 选择需要的功能
? Please pick a preset: Manually select features
? Check the features needed for your project:
◉ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
◉ Router
◉ Vuex
◉ CSS Pre-processors
◉ Linter / Formatter
◉ Unit Testing
❯◉ E2E Testing
# 使用 history 模式
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y
# scss
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default):
Sass/SCSS (with dart-sass)
❯ Sass/SCSS (with node-sass)
Less
Stylus
# esLint
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config:
ESLint with error prevention only
ESLint + Airbnb config
❯ ESLint + Standard config
ESLint + Prettier
# 保存时进行检测
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save
◯ Lint and fix on commit
# 单元测试 mocha + chai
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: (Use arrow keys)
❯ Mocha + Chai
Jest
# 端到端测试
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Mocha
? Pick a E2E testing solution: (Use arrow keys)
❯ Cypress (Chrome only)
Nightwatch (WebDriver-based)
# 单独的配置文件
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Mocha
? Pick a E2E testing solution: Cypress
? Where do you prefer placing config for Babel, ESLint, etc.?
❯ In dedicated config files
In package.json
# 不保存配置
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Mocha
? Pick a E2E testing solution: Cypress
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) n
# 开始创建项目
Vue CLI v4.1.2
✨ Creating project in /Users/heyi/File/Front-End/demo/vue-unit.
🗃 Initializing git repository...
⚙ Installing CLI plugins. This might take a while...
···
📄 Generating README.md...
🎉 Successfully created project vue-cli4-unit-mocha-chai-nyc.
👉 Get started with the following commands:
$ cd vue-cli4-unit-mocha-chai-nyc
$ npm run serve
创建项目完成后,可以发现 cli4 会自动生成一个默认的单元测试例子 tests/unit/ example.spec.js,其内容如下:
import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).to.include(msg)
})
})
接下来,进入项目并运行单元测试:
cd vue-cli4-unit-mocha-chai-nyc
npm run test:unit
> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit
WEBPACK Compiling...
[=== ] 10% (building)Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
[=========================] 98% (after emitting)
DONE Compiled successfully in 3019ms
[=========================] 100% (completed)
WEBPACK Compiled successfully in 3019ms
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed
1 passing (26ms)
MOCHA Tests completed successfully
npm run test:unit
命令会自动读取 tests/unit/ 目录下的 *.spec.js 文件,来跑我们的单元测试。
可以发现,通过 vue-cli4 创建集成的单元测试项目,已经可以让我们做到零配置来写单元测试了。接下来就动手写一个单元测试试试。
2、动手写第一个单元测试
首先我们在 src/components/ 目录下创建文件 AppEmpty.vue,其内容如下:
<template>
<div>{{text}}</div>
</template>
<script>
export default {
name: 'AppEmpty',
props: {
text: {
type: String,
default: 'no data'
}
},
data () {
return {}
}
}
</script>
<style scoped lang="scss">
</style>
接着在 tests/unit/ 目录下创建 app-empty.spec.js 文件,并添加如下内容:
import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import AppEmpty from '@/components/AppEmpty.vue'
describe('AppEmpty.vue', () => {
it('no props.text when passed', () => {
const wrapper = shallowMount(AppEmpty)
expect(wrapper.text()).to.include('no data')
})
it('renders props.text when passed', () => {
const text = '暂无数据'
const wrapper = shallowMount(HelloWorld, {
propsData: { text }
})
expect(wrapper.text()).to.include(text)
})
})
最后,再次运行 test:unit 命令:
npm run test:unit
> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit
WEBPACK Compiling...
[=========================] 98% (after emitting)
DONE Compiled successfully in 1935ms
[=========================] 100% (completed)
WEBPACK Compiled successfully in 1935ms
MOCHA Testing...
AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
HelloWorld.vue
✓ renders props.msg when passed
3 passing (35ms)
MOCHA Tests completed successfully
可以看到,我们自己写的单元测试也跑了起来。
3、单元测试文件书写位置调整
如果所有的 *.spec.js 文件都写在 tests/unit/ 目录下会显得很凌乱,那么如果我们在 /unit 目录下再新建文件夹,会被读取到吗?答案是肯定的。
在 tests/unit/ 目录下新建一个 components 文件夹,把刚刚创建的 app-empty.spec.js 文件移进去。再次运行 test:unit 命令:
npm run test:unit
> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit
WEBPACK Compiling...
[=========================] 98% (after emitting)
DONE Compiled successfully in 2035ms
[=========================] 100% (completed)
WEBPACK Compiled successfully in 2035ms
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed
AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
3 passing (57ms)
MOCHA Tests completed successfully
说明我们是可以自己建立文件夹进行整理我们的单元测试文件的。
如果我们的项目是一个去中心化的架构,我们可能希望我们的单元测试文件位于我们的组件旁边,而不是都写在 tests/unit/ 目录下。
如何实现呢?
在 tests/unit 目录下新建 index.spec.js 文件,其内容如下:
// require all src files that ends with .spec.js
const srcContext = require.context('../../src/', true, /\.spec.js$/)
srcContext.keys().forEach(srcContext)
就可以在 src/ 目录下任何位置写我们的单元测试文件了。
为了验证所言非虚,简单的验证一下。复制 tests/unit/components/app-empty.spec.js 文件,在 src/components/ 目录下粘贴,然后稍稍修改一下测试套件的文本:
- describe('AppEmpty.vue', () => {
+ describe('src/**/*.spec.js AppEmpty.vue', () => {
再次运行 test:unit 命令:
npm run test:unit
> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit
WEBPACK Compiling...
[=========================] 98% (after emitting)
DONE Compiled successfully in 1968ms
[=========================] 100% (completed)
WEBPACK Compiled successfully in 1968ms
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed
src/**/*.spec.js AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
5 passing (38ms)
MOCHA Tests completed successfully
嗯,所言不虚。
4、使用 mochawesome 输出结果
到目前为止,我们的测试报告都是在命令行展示,那么如果我们想要把结果输出并展示在页面上如何做到呢?
首先在项目中安装 mochawesome:
npm install --save-dev mochawesome
然后在 package.json 中修改 test:unit 命令如下:
"test:unit": "vue-cli-service test:unit --reporter=mochawesome",
然后再次执行:
npm run test:unit
> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit --reporter=mochawesome
WEBPACK Compiling...
[=== ] 10% (building)Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
[=========================] 98% (after emitting)
WARNING Compiled with 1 warnings
warning in ./src/components/app-empty.spec.js
Module Error (from ./node_modules/eslint-loader/index.js):
/Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/src/components/app-empty.spec.js
5:1 error 'describe' is not defined no-undef
6:3 error 'it' is not defined no-undef
11:3 error 'it' is not defined no-undef
✖ 3 problems (3 errors, 0 warnings)
@ ./src sync \.spec.js$
@ ./tests/unit/index.spec.js
@ ./node_modules/mochapack/lib/entry.js
[=========================] 100% (completed)
WEBPACK Compiled with 1 warning(s)
Warning in ./src/components/app-empty.spec.js
Module Error (from ./node_modules/eslint-loader/index.js):
/Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/src/components/app-empty.spec.js
5:1 error 'describe' is not defined no-undef
6:3 error 'it' is not defined no-undef
11:3 error 'it' is not defined no-undef
✖ 3 problems (3 errors, 0 warnings)
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed
src/**/*.spec.js AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
5 passing (35ms)
[mochawesome] Report JSON saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.json
[mochawesome] Report HTML saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.html
非常醒目的警告提示首先被发现:
WEBPACK Compiled with 1 warning(s)
Warning in ./src/components/app-empty.spec.js
Module Error (from ./node_modules/eslint-loader/index.js):
/Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/src/components/app-empty.spec.js
5:1 error 'describe' is not defined no-undef
6:3 error 'it' is not defined no-undef
11:3 error 'it' is not defined no-undef
✖ 3 problems (3 errors, 0 warnings)
告诉我们在 ./src/components/app-empty.spec.js 文件中 describe、it 未定义,为什么上面我们执行的时候都没有这个警告,这次就有了呢?而且,为什么只有 src/ 目录下的单元测试文件报了,tests/unit/ 目录下的没有报呢?
答案在项目的 .eslintrc.js 配置文件里:
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/src/**/*.spec.{j,t}s?(x)'
],
env: {
mocha: true
}
}
]
}
在项目生成的 eslint 配置文件中,存在针对特定文件的配置,具体理解不细说,只需要简单的在其中加入 src/ 的配置即可:
···
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
+ '**/tests/unit/**/*.spec.{j,t}s?(x)',
'**/src/**/*.spec.{j,t}s?(x)'
],
env: {
mocha: true
}
}
]
···
接下来我们再次运行 test:unit 命令,上面出现的告警将不复存在:
npm run test:unit
> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit --reporter=mochawesome
WEBPACK Compiling...
[=========================] 98% (after emitting)
DONE Compiled successfully in 1212ms
[=========================] 100% (completed)
WEBPACK Compiled successfully in 1212ms
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed
src/**/*.spec.js AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
5 passing (36ms)
[mochawesome] Report JSON saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.json
[mochawesome] Report HTML saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.html
MOCHA Tests completed successfully
处理了告警问题,回到最初的目的。
命令执行完成后,会在项目根目录下增加一个名为 mochawesome-report 的文件夹,里面就放着输出的单元测试报告。
我们在浏览器打开 mochawesome.html 可以看到如下页面:
单元测试写了,测试报告也有了,接下来就差扫描项目单元测试覆盖率了。
5、使用 nyc 扫描测试覆盖率
文档地址:https://istanbul.js.org/
项目地址:https://github.com/istanbuljs/nyc
首先,在项目中安装所需依赖:
npm i --save-dev babel-plugin-istanbul istanbul-instrumenter-loader nyc
接着在项目根目录下新建 nyc.config.js 文件,其内容如下:
// 探索istanbul/nyc代码覆盖工具的原理 https://zhuanlan.zhihu.com/p/88524418
module.exports = {
'check-coverage': true,
'per-file': true,
'lines': 0,
'statements': 0,
'functions': 0,
'branches': 0,
'include': [
'src/**/*.js',
'src/**/*.vue'
],
'exclude': [
'src/abandon-ui/**',
'src/*.js',
'**/*.spec.js'
],
'reporter': [
'lcov',
'text',
'text-summary'
],
'extension': [
'.js',
'.vue'
],
'cache': true,
'all': true
}
完成后,在项目根目录新建 vue.config.js, 其内容如下:
const path = require('path')
const testMode = process.env.NODE_ENV === 'test'
module.exports = {
lintOnSave: false,
productionSourceMap: false,
chainWebpack: config => {
if (testMode) {
config.merge({
devtool: 'eval'
})
config.module
.rule('istanbul')
.test(/\.(js|vue)$/)
.include
.add(path.resolve(__dirname, '/src'))
.end()
.use('istanbul-instrumenter-loader')
.loader('istanbul-instrumenter-loader')
.options({ esModules: true })
.before('babel-loader')
}
}
}
完成后,打开项目根目录下的 babel.config.js 文件,修改如下:
+ const testMode = process.env.NODE_ENV === 'test'
+
module.exports = {
- presets: ['@vue/cli-plugin-babel/preset']
+ presets: ['@vue/cli-plugin-babel/preset'],
+ plugins: testMode ? ['babel-plugin-istanbul'] : []
}
最后,打开 package.json 文件,修改 test:unit 命令:
- "test:unit": "vue-cli-service test:unit --reporter=mochawesome",
+ "test:unit": "nyc vue-cli-service test:unit --reporter=mochawesome",
再次执行新的 test:unit 命令:
npm run test:unit
> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> nyc vue-cli-service test:unit --reporter=mochawesome
WEBPACK Compiling...
[=== ] 10% (building)Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
[=========================] 98% (after emitting)
DONE Compiled successfully in 2210ms
[=========================] 100% (completed)
WEBPACK Compiled successfully in 2210ms
MOCHA Testing...
HelloWorld.vue
✓ renders props.msg when passed (41ms)
src/**/*.spec.js AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
AppEmpty.vue
✓ no props.text when passed
✓ renders props.text when passed
5 passing (72ms)
[mochawesome] Report JSON saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.json
[mochawesome] Report HTML saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.html
MOCHA Tests completed successfully
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 16.67 | 100 | 50 | 16.67 |
components | 100 | 100 | 100 | 100 |
AppEmpty.vue | 100 | 100 | 100 | 100 |
HelloWorld.vue | 0 | 0 | 0 | 0 |
router | 0 | 100 | 0 | 0 |
index.js | 0 | 100 | 0 | 0 | 5-23
store | 0 | 100 | 100 | 0 |
index.js | 0 | 100 | 100 | 0 | 4
-----------------|---------|----------|---------|---------|-------------------
=============================== Coverage summary ===============================
Statements : 16.67% ( 1/6 )
Branches : 100% ( 0/0 )
Functions : 50% ( 1/2 )
Lines : 16.67% ( 1/6 )
================================================================================
执行成功后会发现根目录下除了新增加的 mochawesome-report/ 目录,还多了 coverage/ 和 .nyc_output/ 目录,我们的覆盖率结果就输出在 coverage/ 目录下。
在浏览器打开 coverage/ 目录下的 index.html 文件会出现以下页面:
单击页面上的 components 可以看到改目录下的详细 .vue 文件覆盖率情况:
再次单击 AppEmpty.vue 可以看到文件文件内的覆盖情况:
最后列出 package.json 文件,以做参考:
{
"name": "vue-cli4-unit-mocha-chai-nyc",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "nyc vue-cli-service test:unit --reporter=mochawesome",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.4.0",
"@vue/cli-plugin-e2e-cypress": "^4.4.0",
"@vue/cli-plugin-eslint": "^4.4.0",
"@vue/cli-plugin-router": "^4.4.0",
"@vue/cli-plugin-unit-mocha": "^4.4.0",
"@vue/cli-plugin-vuex": "^4.4.0",
"@vue/cli-service": "^4.4.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/test-utils": "^1.0.3",
"babel-eslint": "^10.1.0",
"babel-plugin-istanbul": "^6.0.0",
"chai": "^4.1.2",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"istanbul-instrumenter-loader": "^3.0.1",
"mochawesome": "^6.1.1",
"node-sass": "^4.12.0",
"nyc": "^15.0.1",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
}
}
6、使用 karma 扫描测试覆盖率
扫描测试覆盖率的另一个方法是使用 karma。
官网:http://karma-runner.github.io/latest/index.html
在前面的 1-5 节部分,所有的内容都是在第 1 节建立的项目 vue-cli4-unit-mocha-chai-nyc 中依次完成的。在本节内容开始之前,需要按照第 1 节的内容,重新创建一个项目:vue-cli4-unit-mocha-chai-karma 。
首先,依旧是安装需要的依赖:
npm i --save-dev karma karma-mocha mocha karma-chai \
karma-webpack karma-chrome-launcher karma-sourcemap-loader karma-spec-reporter \
karma-coverage@1.1.2 babel-plugin-istanbul@5.1.4 cross-env
在项目根目录创建 karma.conf.js 文件,其内容如下:
const webpackConfig = require('@vue/cli-service/webpack.config.js')
module.exports = function (config) {
config.set({
frameworks: ['mocha'],
files: [
'tests/**/*.spec.js'
],
preprocessors: {
'**/*.spec.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
reporters: ['spec', 'coverage'],
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
},
autoWatch: true,
browsers: ['ChromeHeadless']
})
}
然后,修改 babel.config.js 文件:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
+ env: {
+ test: {
+ plugins: ["istanbul"]
+ }
+ }
}
接着,修改 package.json 文件的 test:unit 命令为:
"test:unit": "cross-env NODE_ENV=test karma start --single-run",
关于这里使用 BABEL_ENV=test
还是 NODE_ENV=test
,建议是 NODE_ENV=test
。
Karma can’t handle split chunks.
- In
@vue/cli-service
v3, unless you explicitly setNODE_ENV
toproduction
,splitChunks
won’t be turned on.- In
@vue/cli-service
v4, unless you explicitly setNODE_ENV
totest
,splitChunks
will be turned on by default.
It is a good practice to always set NODE_ENV
to test
during unit testing because many of the packages in Node.js ecosystem follows this convention.
Actually, with
NODE_ENV=test
, you can skipBABEL_ENV=test
because it will default toNODE_ENV
.
最后,我们执行 test:unit 命令:
npm run test:unit
> vue-cli4-unit-mocha-chai-karma@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-karma
> cross-env NODE_ENV=test karma start --single-run
DONE Compiled successfully in 671ms
ℹ 「wdm」: Hash: f44d39181b90d9235a4b
Version: webpack 4.43.0
Time: 671ms
Built at: 2020-05-29 09:55:03
Asset Size Chunks Chunk Names
../../../../../../Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-karma/dist/favicon.ico 4.19 KiB [emitted]
app.js 1.22 MiB app [emitted] app
img/logo.82b9c7a5.png 6.69 KiB [emitted]
index.html 889 bytes [emitted]
tests/unit/example.spec.js 3.1 MiB tests/unit/example.spec [emitted] tests/unit/example.spec
Entrypoint app = app.js
Entrypoint tests/unit/example.spec = tests/unit/example.spec.js
[0] multi ./src/main.js 28 bytes {app}
[./node_modules/@babel/runtime/helpers/interopRequireDefault.js] 147 bytes {app} {tests/unit/example.spec} [built]
[./node_modules/@vue/test-utils/dist/vue-test-utils.js] 400 KiB {tests/unit/example.spec} [built]
[./node_modules/chai/index.js] 40 bytes {tests/unit/example.spec} [built]
[./node_modules/chai/lib/chai.js] 1.23 KiB {tests/unit/example.spec} [built]
[./node_modules/vue-loader/lib/runtime/componentNormalizer.js] 2.71 KiB {app} {tests/unit/example.spec} [built]
[./node_modules/vue-template-compiler/index.js] 865 bytes {tests/unit/example.spec} [built]
[./node_modules/vue/dist/vue.runtime.esm.js] 222 KiB {app} {tests/unit/example.spec} [built]
[./src/App.vue] 566 bytes {app} [built]
[./src/components/HelloWorld.vue] 599 bytes {app} {tests/unit/example.spec} [built]
[./src/components/HelloWorld.vue?vue&type=script&lang=js&] 576 bytes {app} {tests/unit/example.spec}
[./src/components/HelloWorld.vue?vue&type=template&id=469af010&scoped=true&] 427 bytes {app} {tests/unit/example.spec}
[./src/main.js] 2.38 KiB {app} [built]
[./src/router/index.js] 3.23 KiB {app} [built]
[./tests/unit/example.spec.js] 3.27 KiB {tests/unit/example.spec} [built]
+ 160 hidden modules
Child html-webpack-plugin for "index.html":
Asset Size Chunks Chunk Names
index.html 534 KiB 1
Entrypoint undefined = index.html
[./node_modules/html-webpack-plugin/lib/loader.js!./public/index.html] 928 bytes {1} [built]
[./node_modules/lodash/lodash.js] 528 KiB {1} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {1} [built]
ℹ 「wdm」: Compiled successfully.
29 05 2020 09:55:03.205:INFO [karma-server]: Karma v5.0.9 server started at http://0.0.0.0:9876/
29 05 2020 09:55:03.207:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
29 05 2020 09:55:03.231:INFO [launcher]: Starting browser ChromeHeadless
29 05 2020 09:55:03.790:INFO [Chrome Headless 83.0.4103.61 (Mac OS 10.14.6)]: Connected on socket WTpGcR9Fum14KL6rAAAA with id 16752070
HelloWorld.vue
✓ renders props.msg when passed
Chrome Headless 83.0.4103.61 (Mac OS 10.14.6): Executed 1 of 1 SUCCESS (0.012 secs / 0.009 secs)
TOTAL: 1 SUCCESS
=============================== Coverage summary ===============================
Statements : 100% ( 6/6 )
Branches : 100% ( 0/0 )
Functions : 100% ( 3/3 )
Lines : 100% ( 6/6 )
================================================================================
执行成功后会在项目根目录下生成 coverage/** 文件夹,放着输出的测试覆盖率,我们在浏览器打开 coverage/lcov-report/index.html 文件,其页面如下:
与使用 nyc 输出的页面一样,也可以单击列表的 src/components/ 查看该目录下的详细文件:
查看 HelloWorld.vue 文件详情:
由于在写这篇记录的时候,发现存在依赖(karma-coverage、babel-plugin-istanbul)不能直接安装最新版本,下面列出完整的 package.json 文件,以做参考:
{
"name": "vue-cli4-unit-mocha-chai-karma",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "cross-env NODE_ENV=test karma start --single-run",
"test:e2e": "vue-cli-service test:e2e",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.4.0",
"@vue/cli-plugin-e2e-cypress": "^4.4.0",
"@vue/cli-plugin-eslint": "^4.4.0",
"@vue/cli-plugin-router": "^4.4.0",
"@vue/cli-plugin-unit-mocha": "^4.4.0",
"@vue/cli-plugin-vuex": "^4.4.0",
"@vue/cli-service": "^4.4.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/test-utils": "^1.0.3",
"babel-eslint": "^10.1.0",
"babel-plugin-istanbul": "^5.1.4",
"chai": "^4.1.2",
"cross-env": "^7.0.2",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"karma": "^5.0.9",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-coverage": "^1.1.2",
"karma-mocha": "^2.0.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^4.0.2",
"mocha": "^7.2.0",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
}
}
使用 karma 其它常用依赖:
chai-spies karma-chai-spies \
sinon sinon-chai karma-sinon-chai