使用Karma、Mocha实现vue单元测试

Karma

Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner)。该工具在Vue中的主要作用是将项目运行在各种主流Web浏览器进行测试。
换句话说,它是一个测试工具,能让你的代码在浏览器环境下测试。需要它的原因在于,你的代码可能是设计在浏览器端执行的,在node环境下测试可能有些bug暴露不出来;另外,浏览器有兼容问题,karma提供了手段让你的代码自动在多个浏览器(chrome,firefox,ie等)环境下运行。如果你的代码只会运行在node端,那么你不需要用karma。

Mocha

Mocha是一个测试框架,在vue-cli中配合chai断言库实现单元测试。
Mocha的常用命令和用法不算太多,看阮一峰老师的测试框架 Mocha 实例教程就可以大致了解了。
而Chai断言库可以看Chai.js断言库API中文文档,很简单,多查多用就能很快掌握。

我对测试框架的理解

npm run unit 执行过程

  1. 执行 npm run unit 命令
  2. 开启Karma运行环境
  3. 使用Mocha去逐个测试用Chai断言写的测试用例
  4. 在终端显示测试结果
  5. 如果测试成功,karma-coverage 会在 ./test/unit/coverage 文件夹中生成测试覆盖率结果的网页。

Karma

对于Karma,我只是了解了一下它的配置选项
下面是Vue的karma配置,简单注释了下:

var webpackConfig = require(‘../../build/webpack.test.conf‘)

module.exports = function (config) {
  config.set({
    // 浏览器
    browsers: [‘PhantomJS‘],
    // 测试框架
    frameworks: [‘mocha‘, ‘sinon-chai‘, ‘phantomjs-shim‘],
    // 测试报告
    reporters: [‘spec‘, ‘coverage‘],
    // 测试入口文件
    files: [‘./index.js‘],
    // 预处理器 karma-webpack
    preprocessors: {
      ‘./index.js‘: [‘webpack‘, ‘sourcemap‘]
    },
    // Webpack配置
    webpack: webpackConfig,
    // Webpack中间件
    webpackMiddleware: {
      noInfo: true
    },
    // 测试覆盖率报告
    // https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md
    coverageReporter: {
      dir: ‘./coverage‘,
      reporters: [
        { type: ‘lcov‘, subdir: ‘.‘ },
        { type: ‘text-summary‘ }
      ]
    }
  })
}

Mocha和chai

我们看下官方的例子(都用注释来解释代码意思了):

import Vue from ‘vue‘ // 导入Vue用于生成Vue实例
import Hello from ‘@/components/Hello‘ // 导入组件
// 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
describe(‘Hello.vue‘, () => {
  // 每个describe块应该包括一个或多个it块,称为测试用例(test case)
  it(‘should render correct contents‘, () => {
    const Constructor = Vue.extend(Hello) // 获得Hello组件实例
    const vm = new Constructor().$mount() // 将组件挂在到DOM上
    //断言:DOM中class为hello的元素中的h1元素的文本内容为Welcome to Your Vue.js App
    expect(vm.$el.querySelector(‘.hello h1‘).textContent)
      .to.equal(‘Welcome to Your Vue.js App‘)
  })
})

需要知道的知识点:

  • 测试脚本都要放在 test/unit/specs/ 目录下。
  • 脚本命名方式为 [组件名].spec.js
  • 所谓断言,就是对组件做一些操作,并预言产生的结果。如果测试结果与断言相同则测试通过。
  • 单元测试默认测试 src 目录下除了 main.js 之外的所有文件,可在 test/unit/index.js 文件中修改。
  • Chai断言库中,to be been is that which and has have with at of same 这些语言链是没有意义的,只是便于理解而已。
  • 测试脚本由多个 descibe 组成,每个 describe 由多个 it 组成。
  • 了解异步测试
    it(‘异步请求应该返回一个对象‘, done => {
      request
      .get(‘https://api.github.com‘)
      .end(function(err, res){
        expect(res).to.be.an(‘object‘);
        done();
      });
    });
  • 了解一下 describe 的钩子(生命周期)
    describe(‘hooks‘, function() {
    
    before(function() {
      // 在本区块的所有测试用例之前执行
    });
    
    after(function() {
      // 在本区块的所有测试用例之后执行
    });
    
    beforeEach(function() {
      // 在本区块的每个测试用例之前执行
    });
    
    afterEach(function() {
      // 在本区块的每个测试用例之后执行
    });
    
    // test cases
    });

实践

上面简单介绍了单元测试的用法,下面来动手在Vue中进行单元测试!

util.js

从Vue官方的demo可以看出,对于Vue的单元测试我们需要将组件实例化为一个Vue实例,有时还需要挂载到DOM上。

 const Constructor = Vue.extend(Hello) // 获得Hello组件实例
 const vm = new Constructor().$mount() // 将组件挂载到DOM上

以上写法只是简单的获取组件,有时候我们需要传递props属性、自定义方法等,还有可能我们需要用到第三方UI框架。所以以上写法非常麻烦。
这里推荐Element的单元测试工具脚本Util.js,它封装了Vue单元测试中常用的方法。下面demo也是根据该 Util.js来写的。
这里简单注释了下各方法的用途。

/**
 * 回收 vm,一般在每个测试脚本测试完成后执行回收vm。
 * @param  {Object} vm
 */
exports.destroyVM = function (vm) {}

/**
 * 创建一个 Vue 的实例对象
 * @param  {Object|String}  Compo     - 组件配置,可直接传 template
 * @param  {Boolean=false}  mounted   - 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createVue = function (Compo, mounted = false) {}

/**
 * 创建一个测试组件实例
 * @param  {Object}  Compo          - 组件对象
 * @param  {Object}  propsData      - props 数据
 * @param  {Boolean=false} mounted  - 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createTest = function (Compo, propsData = {}, mounted = false) {}

/**
 * 触发一个事件
 * 注: 一般在触发事件后使用 vm.$nextTick 方法确定事件触发完成。
 * mouseenter, mouseleave, mouseover, keyup, change, click 等
 * @param  {Element} elm      - 元素
 * @param  {String} name      - 事件名称
 * @param  {*} opts           - 配置项
 */
exports.triggerEvent = function (elm, name, ...opts) {}

/**
 * 触发 “mouseup” 和 “mousedown” 事件,既触发点击事件。
 * @param {Element} elm     - 元素
 * @param {*} opts          - 配置选项
 */
exports.triggerClick = function (elm, ...opts) {}

示例一

示例一中我们测试了 Hello 组件的各种元素的数据,学习 util.js 的 destroyVM 和 createTest 方法的用法以及如何获取目标元素进行测试。获取DOM元素的方式可查看DOM 对象教程。
Hello.vue

<template>
  <div class="hello">
    <h1 class="hello-title">{{ msg }}</h1>
    <h2 class="hello-content">{{ content }}</h2>
  </div>
</template>

<script>
export default {
  name: ‘hello‘,
  props: {
    content: String
  },
  data () {
    return {
      msg: ‘Welcome!‘
    }
  }
}
</script>

Hello.spec.js

import { destroyVM, createTest } from ‘../util‘
import Hello from ‘@/components/Hello‘

describe(‘Hello.vue‘, () => {
  let vm

  afterEach(() => {
    destroyVM(vm)
  })

  it(‘测试获取元素内容‘, () => {
    vm = createTest(Hello, { content: ‘Hello World‘ }, true)
    expect(vm.$el.querySelector(‘.hello h1‘).textContent).to.equal(‘Welcome!‘)
    expect(vm.$el.querySelector(‘.hello h2‘).textContent).to.have.be.equal(‘Hello World‘)
  })

  it(‘测试获取Vue对象中数据‘, () => {
    vm = createTest(Hello, { content: ‘Hello World‘ }, true)
    expect(vm.msg).to.equal(‘Welcome!‘)
    // Chai的语言链是无意义的,可以随便写。如下:
    expect(vm.content).which.have.to.be.that.equal(‘Hello World‘)
  })

  it(‘测试获取DOM中是否存在某个class‘, () => {
    vm = createTest(Hello, { content: ‘Hello World‘ }, true)
    expect(vm.$el.classList.contains(‘hello‘)).to.be.true
    const title = vm.$el.querySelector(‘.hello h1‘)
    expect(title.classList.contains(‘hello-title‘)).to.be.true
    const content = vm.$el.querySelector(‘.hello-content‘)
    expect(content.classList.contains(‘hello-content‘)).to.be.true
  })
})

输出结果

Hello.vue
  √ 测试获取元素内容
  √ 测试获取Vue对象中数据
  √ 测试获取DOM中是否存在某个class

示例二

示例二中我们使用 createTest 创建测试组件测试点击事件,用 createVue 创建Vue示例对象测试组件 Click 的使用。这里主要可以看下到 createVue 方法的使用。
Click.vue

<template>
  <div>
    <span class="init-num">初始值为{{ InitNum }}</span><br>
    <span class="click-num">点击了{{ ClickNum }}次</span><br>
    <span class="result-num">最终结果为{{ ResultNum }}</span><br>
    <button @click="add">累加{{ AddNum }}</button>
  </div>
</template>

<script>
export default {
  name: ‘Click‘,
  props: {
    AddNum: {
      type: Number,
      default: 1
    },
    InitNum: {
      type: Number,
      default: 1
    }
  },
  data () {
    return {
      ClickNum: 0,
      ResultNum: 0
    }
  },
  mounted () {
    this.ResultNum = this.InitNum
  },
  methods: {
    add () {
      this.ResultNum += this.AddNum
      this.ClickNum++
      this.$emit(‘result‘, {
        ClickNum: this.ClickNum,
        ResultNum: this.ResultNum
      })
    }
  }
}
</script>

Click.spec.js

import { destroyVM, createTest, createVue } from ‘../util‘
import Click from ‘@/components/Click‘

describe(‘click.vue‘, () => {
  let vm

  afterEach(() => {
    destroyVM(vm)
  })

  it(‘测试按钮点击事件‘, () => {
    vm = createTest(Click, {
      AddNum: 10,
      InitNum: 11
    }, true)
    let buttonElm = vm.$el.querySelector(‘button‘)
    buttonElm.click()
    buttonElm.click()
    buttonElm.click()
    // setTimeout 的原因
    // 在数据改变之后,界面的变化会有一定延时。不用timeout有时候会发现界面没有变化
    setTimeout(done => {
      expect(vm.ResultNum).to.equal(41)
      expect(vm.$el.querySelector(‘.init-num‘).textContent).to.equal(‘初始值为11‘)
      expect(vm.$el.querySelector(‘.click-num‘).textContent).to.equal(‘点击了3次‘)
      expect(vm.$el.querySelector(‘.result-num‘).textContent).to.equal(‘最终结果为41‘)
      done()
    }, 100)
  })

  it(‘测试创建Vue对象‘, () => {
    let result
    vm = createVue({
      template: `
        <click @click="handleClick"></click>
      `,
      props: {
        AddNum: 10,
        InitNum: 11
      },
      methods: {
        handleClick (obj) {
          result = obj
        }
      },
      components: {
        Click
      }
    }, true)
    vm.$el.click()
    vm.$nextTick(done => {
      expect(result).to.be.exist
      expect(result.ClickNum).to.equal(1)
      expect(result.ResultNum).to.be.equal(21)
      done()
    })
})

输出结果

click.vue
  √ 测试按钮点击事件
  √ 测试创建Vue对象

其他

所有示例代码都放Github仓库中便于查看。如果想查看更多好的测试用例,建议配合 Util.js 看一下 Element 的单元测试脚本的写法,里面有很多测试脚本可以供我们学习。作为被广大Vue用户使用的UI组件库,测试脚本肯定也写很很不错的~甚至可以将这些脚本照抄一遍,相信这会对学习Vue组件的单元测试有很大帮助。

下面是本人看Element单元测试的笔记,供参考。

  • Util.js 方法包含了大多数Vue组件化的测试需求。
  • vm.$el vm.$nextTick 和 vm.$ref 都是在测试过程中比较常用的一些Vue语法糖。
  • 需要注意: vm.$nextTick 方法是异步的,所以需要在里面使用done方法。
  • 异步断言,方法参数需要是 _ 或者 done
  • 大多数时候查询元素通过 querySelector 方法查询class获得
    • vm.$el.querySelector(‘.el-breadcrumb‘).innerText
  • 大多数情况下查询是否存在某个Class通过 classList.contains 方法获得,查找的结果为 true 或 false
    • vm.$el .classList.contains(‘el-button--primary‘)
  • 异步测试必须以 done() 方法结尾。setTimeout 和 vm.$nextTick 是常用的异步测试。
  • 实现按钮点击:通过获取按钮元素 btn,执行 btn.click() 方法实现。
  • 由于 Vue 进行 异步更新DOM 的情况,一些依赖DOM更新结果的断言必须在 Vue.nextTick 回调中进行。
    triggerEvent(vm.$refs.cascader.$el, ‘mouseenter‘);
    vm.$nextTick(_ => {
      vm.$refs.cascader.$el.querySelector(‘.el-cascader__clearIcon‘).click();
      vm.$nextTick(_ => {
         expect(vm.selectedOptions.length).to.be.equal(0);
         done();
      });
    });
时间: 2024-10-10 05:34:36

使用Karma、Mocha实现vue单元测试的相关文章

大前端的自动化工厂(5)—— 基于Karma+Mocha+Chai的单元测试和接口测试

一. 前端自动化测试 大多数前端开发者对测试相关的知识是比较缺乏的,一来是开发节奏很快,来不及写,另一方面团队里也配备了"人肉测试机",完全没必要自己来.但随着项目体量的增大,许多人维护同一份代码,经常会出现有些函数莫名其妙地结果不对了,或者某个接口的入参变了,又或者哪位大哥把后端返回的数据结构给改了.每天工作的时间里被拉来拉去帮人定位问题,结果花了很多时间却发现大部分都是别人的锅.每当遇到项目上线,那就更热闹了,跟着其他"人肉测试机"大家一起点点点...... 很

自动化单元测试(Karma + Mocha)

使用 Karma + Mocha做单元测试 Karma([?kɑrm?] 卡玛)是一个测试运行器,它可以呼起浏览器,加载测试脚本,然后运行测试用例 Mocha([?mo?k?] 摩卡)是一个单元测试框架/库,它可以用来写测试用例 Sinon(西农)是一个 spy / stub / mock 库,用以辅助测试(使用后才能理解) 安装各种工具 npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon s

Vue 单元测试 使用mocha+jest

目录 Vue 单元测试 mocha+jest jest 实例 mocha expect方法断言 示例代码 Vue 单元测试 官网:https://vue-test-utils.vuejs.org/zh 定义: 单元测试是用来对一个模块.一个函数或者一个类来进行正确性检验的测试工作. 指令: package.json文件 "test:unit": "vue-cli-service test:unit" 测试驱动开发(TDD:Test-Driven Developmen

Karma和Jasmine自动化单元测试

前言 在Java领域,Apache, Spring, JBoss 三大社区的开源库,包罗万象,但每个库都在其领域中都鹤立鸡群.而Nodejs中各种各样的开源库,却让人眼花缭乱,不知从何下手. Nodejs领域: Jasmine做单元测试,Karma自动化完成单元测试,Grunt启动Karma统一项目管理,Yeoman最后封装成一个项目原型模板,npm做nodejs的包依赖管理,bower做javascript的包依赖管理.Java领域:JUnit做单元测试, Maven自动化单元测试,统一项目管

结合angularjs,Karma和Jasmine自动化单元测试

前言 在Java领域,Apache, Spring, JBoss 三大社区的开源库,包罗万象,但每个库都在其领域中都鹤立鸡群.而Nodejs中各种各样的开源库,却让人眼花缭乱,不知从何下手. Nodejs领域: Jasmine做单元测试,Karma自动化完成单元测试,Grunt启动Karma统一项目管理,Yeoman最后封装成一个项目原型模板,npm做nodejs的包依赖管理,bower做javascript的包依赖管理. Java领域:JUnit做单元测试, Maven自动化单元测试,统一项目

前端测试 karma mocha should 都是什么鬼?

测试TDD和BDD的区别 TDD是测试驱动开发,通过用测试用例来规范约束开发者,编写出质量更高的代码 BDD是行为驱动开发,描述行为路径,就像描述故事,产品和前线业务人员可参与到开发流程中,减轻测试和开发写测试用例的成本.用通用的语言形式尽可能避免沟通上的障碍,实现产品和开发者同时定义系统的需求. karma  mocha  should  这些都是什么鬼? karma 是驱动测试的runner,可以执行Javascript代码在多个真实的浏览器中测试.并生成测试报告 安装 Karma :  $

karma mocha angular angular-mock 测试

describe('工具方法测试', function () { var utilsModule; beforeEach(function () { module('Admin'); // module('app.menu'); // utilsModule = module('blocks.utils'); } ); it('数组转换方法测试', function () { var dataset = []; var arr = []; var level = 9999; // var uti

Angularjs 基于karma和jasmine的单元测试

目录: 1. 单元测试的配置 2. 实例文件目录解释 3. 测试controller     3.1 测试controller中变量值是否正确     3.2 模拟http请求返回值,测试$http服务相关 4. 从文件中读取json,来模拟 http请求返回数据 5. 测试返回promise的service 已经有很多教程提到了angularjs项目的单元测试,但大都不是很全,如一些入门的文章,介绍了测试http service 却没有介绍如何从文件中读取测试数据来仿真.一些介绍如何从文件中读

带你入门带你飞Ⅰ 使用Mocha + Chai + Sinon单元测试Node.js

目录 1. 简介 2. 前提条件 3. Mocha入门 4. Mocha实战 被测代码 Example 1 Example 2 Example 3 5. Troubleshooting 6. 参考文档 简介 Mocha 是具有丰富特性的 JavaScript 测试框架,可以运行在 Node.js 和浏览器中,使得异步测试更简单更有趣.Mocha 可以持续运行测试,支持灵活又准确的报告,当映射到未捕获异常时转到正确的测试示例. Chai 是一个针对 Node.js 和浏览器的行为驱动测试和测试驱动