美文网首页前端让前端飞程序员
Vue单元测试实战教程(Mocha/Karma + Vue-Te

Vue单元测试实战教程(Mocha/Karma + Vue-Te

作者: 熠辉web3 | 来源:发表于2017-12-31 14:40 被阅读1052次

    在《前端进阶之路: 前端架构设计(3) - 测试核心》这边文章中, 通过分析了"传统手工测试的局限性" 去引出了测试驱动开发的理念, 并介绍了一些测试工具. 这篇文章我将通过一个Vue的项目, 去讲解如何使用mocha & karma, 且结合vue官方推荐的vue-test-utils去进行单元测试的实战.

    一. 安装

    我为本教程写一个示例库, 您可以直接跳过所有安装过程, 安装依赖后运行该示例项目:

    如果想一步步进行安装, 也可以跟着下面的步骤进行操作:

    (一) 使用脚手架初始化vue项目(使用webpack模板)

    //命令行中输入(默认阅读该文章的读者已经安装vue-cli和node环境)
    vue init webpack vueunittest
    

    注意, 当询问到这一步Pick a test runner(Use arrow keys)时, 请选择使用Karma and Mocha

    选择Karma ad Mocha选择Karma ad Mocha

    接下来的操作进入项目npm install安装相关依赖后(该步骤可能更会出现PhantomJS这个浏览器安装失败的报错, 不用理会, 因为 之后我们不使用这个浏览器), npm run build即可.

    (二) 安装Karma-chrome-launch

    接下来安装karma-chrome-launcher, 在命令行中输入

    npm install karma-chrome-launcher --save-dev
    

    然后在项目中找到test/unit/karma.conf.js文件, 将PhantomJS浏览器修改为Chrome不要问我为什么不使用PhantomJS, 因为经常莫名的错误, 改成Chrome就不会!!!)

    //karma.conf.js
    
    var webpackConfig = require('../../build/webpack.test.conf')
    
    module.exports = function (config) {
      config.set({
        //browsers: ['PhantomJS'],
        browsers: ['Chrome'],    
        
        ...
      })
    }
    

    (三) 安装Vue-test-utils

    安装Vue.js 官方的单元测试实用工具库, 在命令行输入:

    npm install --save-dev vue-test-utils
    

    (四) 执行npm run unit

    当你完成以上两步的时候, 你就可以在命令行执行npm run unit尝鲜你的第一次单元测试了, Vue脚手架已经初始化了一个HelloWorld.spec.js的测试文件去测试HelloWrold.vue, 你可以在test/unit/specs/HelloWorld.spec.js下找到这个测试文件.(提示: 将来所有的测试文件, 都将放specs这个目录下, 并以测试脚本名.spec.js结尾命名!)

    在命令行输入npm run unit, 当你看到下图所示的一篇绿的时候, 说明你的单元测试通过了!

    第一次单元测试测试通过第一次单元测试测试通过

    二. 测试工具的使用方法

    下面是一个Counter.vue文件, 我将以该文件为基础讲解项目中测试工具的使用方法.

    //Counter.vue
    
    <template>
      <div>
        <h3>Counter.vue</h3>
        {{ count }}
        <button @click="increment">自增</button>
      </div>
    </template>
    
    <script>
      export default {
        data () {
          return {
            count: 0
          }
        },
    
        methods: {
          increment () {
            this.count++
          }
        }
      }
    </script>
    

    (一) Mocha框架

    1. Mocha测试脚本的写法

    Mocha的作用是运行测试脚本, 要对上面Counter.vue进行测试, 我们就要写测试脚本, 通常测试脚本应该与Vue组件名相同, 后缀为spec.js. 比如, Counter.vue组件的测试脚本名字就应该为Counter.spec.js

    //Counter.spec.js
    
    import Vue from 'vue'
    import Counter from '@/components/Counter'
    
    describe('Counter.vue', () => {
    
      it('点击按钮后, count的值应该为1', () => {
        //获取组件实例
        const Constructor = Vue.extend(Counter);
        //挂载组件
        const vm = new Constructor().$mount();
        //获取button
        const button = vm.$el.querySelector('button');
        //新建点击事件
        const clickEvent = new window.Event('click');
        //触发点击事件
        button.dispatchEvent(clickEvent);
        //监听点击事件
        vm._watcher.run();
        // 断言:count的值应该是数字1
        expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);
      })
    
    })
    

    上面这段代码就是一个测试脚本.测试脚本应该包含一个或多个describe, 每个describe块应该包括一个或多个it

    describe块称为"测试套件"(test suite), 表示一组相关的测试. 它是一个函数, 第一个参数是测试套件的名称(通常写测试组件的名称, 这里即为Counter.js), 第二个参数是一个实际执行的函数.

    it块称为"测试用例"(test case), 表示一个单独的测试, 是测试的最小单位. 它也是一个函数, 第一个参数是测试用例的名称(通常描述你的断言结果, 这里即为"点击按钮后, count的值应该为1"), 第二个参数是一个实际执行的函数.

    2. Mocha进行异步测试

    我们在Counter.vue组件中添加一个按钮, 并添加一个异步自增的方法为incrementByAsync, 该函数设置一个延时器, 1000ms后count自增1.

      <template>
        ...
        <button @click="increment">自增</button>
        <button @click="incrementByAsync">异步自增</button>
        ...
      <template>
      
      <script>
         ...
         methods: {
          ...
          incrementByAsync () {
            window.setTimeout(() => {
              this.count++;
            }, 1000) 
          }
        }
      </script>
    

    给测试脚本中新增一个测试用例, 也就是it()

      it('count异步更新, count的值应该为1', (done) => {
        ///获取组件实例
        const Constructor = Vue.extend(Counter);
        //挂载组件
        const vm = new Constructor().$mount();
        //获取button
        const button = vm.$el.querySelectorAll('button')[1];
        //新建点击事件
        const clickEvent = new window.Event('click');
    
        //触发点击事件
        button.dispatchEvent(clickEvent);
        //监听点击事件
        vm._watcher.run();
        //1s后进行断言
        window.setTimeout(() => {
          // 断言:count的值应该是数字1
          expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);
          done();
        }, 1000);
      })
    

    Mocha中的异步测试, 需要给it()内函数的参数中添加一个done, 并在异步执行完后必须调用done(), 如果不调用done(), 那么Mocha会在2000ms后报错且本次单元测试测试失败(mocha默认的异步测试超时上线为2000ms), 错误信息如下:

    未调用done()的报错未调用done()的报错

    3. Mocha的测试钩子

    如果大家对于vue的mounted(), created()钩子能够理解的话, 对Mocha的钩子也很容易理解, Mocha在describe块中提供了四个钩子: before(), after(), beforeEach(), afterEach(). 它们会在以下时间执行

    describe('钩子说明', function() {
    
      before(function() {
        // 在本区块的所有测试用例之前执行
      });
    
      after(function() {
        // 在本区块的所有测试用例之后执行
      });
    
      beforeEach(function() {
        // 在本区块的每个测试用例之前执行
      });
    
      afterEach(function() {
        // 在本区块的每个测试用例之后执行
      });
    
    });
    

    上述就是Mocha的基本使用介绍, 如果想了解Mocha的更多使用方法, 可以查看下面的文档和一篇阮一峰的Mocha教程:

    (二) Chai断言库

    上面的测试用例中, 以expect()方法开头的就是断言.

    expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);
    

    所谓断言, 就是判断源码的实际执行结果与预期结果是否一致, 如果不一致, 就会抛出错误. 上面的断言的意思是指: 有.num这类名的节点的内容应该为数字1. 断言库库有很多种, Mocha并不限制你需要使用哪一种断言库, Vue的脚手架提供的断言库是sino-chai, 是一个基于Chai的断言库, 并且我们指定使用的是它的expect断言风格.

    expect断言风格的优点很接近于自然语言, 下面是一些例子

      // 相等或不相等
      expect(1 + 1).to.be.equal(2);
      expect(1 + 1).to.be.not.equal(3);
    
      // 布尔值为true
      expect('hello').to.be.ok;
      expect(false).to.not.be.ok;
    
      // typeof
      expect('test').to.be.a('string');
      expect({ foo: 'bar' }).to.be.an('object');
      expect(foo).to.be.an.instanceof(Foo);
    
      // include
      expect([1,2,3]).to.include(2);
      expect('foobar').to.contain('foo');
      expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
    
      // empty
      expect([]).to.be.empty;
      expect('').to.be.empty;
      expect({}).to.be.empty;
      
      // match
      expect('foobar').to.match(/^foo/);
    
    

    每一个it()所包裹的测试用例都应该有一句或多句断言,上面只是介绍了一部分的断言语法, 如果想要知道更多Chai的断言语法, 请查看以下的官方文档.

    (三) Vue-test-utils测试库

    1. 在测试脚本中引入vue-test-utils

    //Counter.spec.js
    
    import Vue from 'vue'
    import Counter from '@/components/Counter'
    //引入vue-test-utils
    import {mount} from 'vue-test-utils'
    

    2. 测试文本内容

    下面我将在Counter.spec.js测试脚本中对Counter.vue<h3>的文本内容进行测试, 大家可以直观的感受一下使用了Vue-test-utils后对.vue单文件组件的测试变得多么简单.

    • 未使用vue-test-utils的测试用例:
      it('未使用Vue-test-utils: 正确渲染h3的文字为Counter.vue', () => {
        const Constructor = Vue.extend(Counter);
        const vm = new Constructor().$mount();
        const H3 = vm.$el.querySelector('h3').textContent;
        expect(H3).to.equal('Counter.vue');
      })
    
    • 使用了vue-test-utils的测试用例:
      it('使用Vue-test-Utils: 正确渲染h3的文字为Counter.vue', () => {
        const wrapper = mount(Counter);
        expect(wrapper.find('h3').text()).to.equal('Counter.vue');
      })
    

    从上面的代码可以看出, vue-test-utils工具将该测试用例的代码量减少了一半, 如果是更复杂的测试用例, 那么代码量的减少将更为突出. 它可以让我们更专注于去写文件的测试逻辑, 将获取组件实例和挂载的繁琐的操作交由vue-test-utils去完成.

    3. vue-test-utils的常用API

    • find(): 返回匹配选择器的第一个DOM节点或Vue组件的wrapper, 可以使用任何有效的选择器
    • text(): 返回wrapper的文本内容
    • html(): 返回wrapper DOM的HTML字符串
      it('find()/text()/html()方法', () => {
        const wrapper = mount(Counter);
        const h3 = wrapper.find('h3');
        expect(h3.text()).to.equal('Counter.vue');
        expect(h3.html()).to.equal('<h3>Counter.vue</h3>');
      })
    
    • trigger(): 在该 wrapper DOM 节点上触发一个事件。
    it('trigger()方法', () => {
        const wrapper = mount(Counter);
        const buttonOfSync = wrapper.find('.sync-button');
        buttonOfSync.trigger('click');
        buttonOfSync.trigger('click');
        const count = Number(wrapper.find('.num').text());
        expect(count).to.equal(2);
      })
    
    • setData(): 设置data的属性并强制更新
      it('setData()方法',() => {
        const wrapper = mount(Counter);
        wrapper.setData({foo: 'bar'});
        expect(wrapper.vm.foo).to.equal('bar');
      })
    

    上面介绍了几个vue-test-utils提供的方法, 如果想深入学习vue-test-utils, 请阅读下面的官方文档:

    三. 项目说明

    该项目模仿了一个简单的微博, 在代码仓库下载后, 可直接通过npm run dev运行.

    (一) 项目效果图

    项目展示项目展示

    (二) 项目中的交互逻辑和需求

    1. 在文本框中输入内容后点击"发布"按钮(1), 会新发布内容到微博列表中, 且个人头像等下的微博数量(6)会增加1个
    2. 当文本框中无内容时, 不能发布空微博到微博列表, 且弹出提示框, 叫用户输入内容
    3. 当点击"关注"(2), 个人头像下关注的数量(5)会增加1个, 且按钮内字体变成"取消关注"; 当点击"取消关注"(2), 个人头像下的数量(5)会减少1个, 且按钮内字体变成"关注"
    4. 当点击"收藏"(3)时, 我的收藏(7)会增加1个数量, 且按钮内文字变成"已收藏"; 点击"已收藏"(3)时, 我的收藏(7)会减少1个数量, 且按钮内文字变成"收藏"
    5. 当点击"赞"(4), 我的赞(8)会增加1个数量, 且按钮内文字变成"取消赞"; 点击"取消赞"(3)时, 我的赞(8)会减少1个数量, 且按钮内文字变成"赞"

    (三) 项目源码

    //SinaWeibo.vue
    
    <template>
      <div class="weibo-page">
        <nav>
          <span class="weibo-logo"></span>
          <div class="search-wrapper">
            <input type="text" placeholder="大家正在搜: 李棠辉的文章好赞!">
            <img v-if="!iconActive" @mouseover="mouseOverToIcon" src="../../static/image/search.png" alt="搜索icon">
            <img v-if="iconActive" @mouseout="mouseOutToIcon" src="../../static/image/search-active.png" alt="搜索icon">
          </div>
        </nav>
        <div class="main-container">
          <aside class="aside-nav">
            <ul>
              <li :class="{ active: isActives[indexOfContent] }" v-for="(content, indexOfContent) in asideTab" :key="indexOfContent" @click="tabChange(indexOfContent)">
                <span>{{content}}</span>
                <span class="count">
                  <span v-if="indexOfContent === 1">({{collectNum}})</span>
                  <span v-if="indexOfContent === 2">({{likeNum}})</span>              
                </span>
              </li>
            </ul>
          </aside>
          <main class="weibo-content">
            <div class="weibo-publish-wrapper">
              <img src="../../static/image/tell-people.png"></img>
              <textarea v-model="newWeiboContent.content"></textarea>
              <button @click="publishNewWeiboContent">发布</button>
            </div>
            <div 
              class="weibo-news" 
              v-for="(news, indexOfNews) in weiboNews"
              :key="indexOfNews">
              <div class="content-wrapper">
                <div class="news-title">
                <div class="news-title__left">
                  <img :src="news.imgUrl">
                  <div class="title-text">
                    <div class="title-name">{{news.name}}</div>
                    <div class="title-time">{{news.resource}}</div>
                  </div>
                </div>
                <button 
                  class="news-title__right add" 
                  v-if="news.attention === false"
                  @click="attention(indexOfNews)">
                  <i class="fa fa-plus"></i>
                  关注
                </button>
                 <button 
                  class="news-title__right cancel" 
                  v-if="news.attention === true"
                  @click="unAttention(indexOfNews)">
                  <i class="fa fa-close"></i>
                  取消关注
                </button>
              </div>
              <div class="news-content">{{news.content}}</div>
              <div class="news-image" v-if="news.images.length">
                <img 
                v-for="(img, indexOfImg) in news.images"
                :key="indexOfImg"
                :src="img">
              </div>
              </div>
              <ul class="news-panel">
                <li @click="handleCollect(indexOfNews)">
                  <i class="fa fa-star-o" :class="{collected: news.collect }"></i>
                  {{news.collect ? "已收藏" : '收藏'}}
                </li>
                <li>
                  <i class="fa fa-external-link"></i>
                  转发
                </li>
                <li>
                  <i class="fa fa-commenting-o"></i>
                  评论
                </li>
                <li @click="handleLike(indexOfNews)">
                  <i class="fa fa-thumbs-o-up" :class="{liked: news.like}"></i>
                  {{news.like ? '取消赞' : '赞'}}
                </li>
              </ul>
            </div>
    
          </main>
          <aside class="aside-right">
            <div class="profile-wrapper">
              <div class="profile-top">
                <img src="../../static/image/profile.jpg">
              </div>
              <div class="profile-bottom">
                <div class="profile-name">Lee_tanghui</div>
                <ul class="profile-info">
                  <li 
                    v-for="(profile, indexOfProfile) in profileData"
                    :key="indexOfProfile">
                    <div class="number">{{profile.num}}</div>
                    <div class="text">{{profile.text}}</div>
                  </li>
                </ul>
              </div>
            </div>
          </aside>
        </div>
        <footer>
           Wish you like my blog! --- LITANGHUI
        </footer>
      </div>
    </template>
    
    <script>
      //引入假数据
      import * as mockData from '../mock-data.js'
    
      export default {
        mounted() {
          //模拟获取数据
          this.profileData = mockData.profileData;
          this.weiboNews = mockData.weiboNews;
          this.collectNum = mockData.collectNum;
          this.likeNum = mockData.likeNum;
    
        },
        data() {
          return {
            iconActive: false,
            asideTab: ["首页", "我的收藏", "我的赞"],
            isActives: [true, false, false],
            profileData: [],
            weiboNews: [],
            collectNum: 0,
            likeNum: 0,
            newWeiboContent:   {
              imgUrl: '../../static/image/profile.jpg',
              name: 'Lee_tanghui',
              resource: '刚刚 来自 网页版微博',
              content: '',
              images: []
            },
          }
        },
        methods: {
          mouseOverToIcon() {
            this.iconActive = true;
          },
          mouseOutToIcon() {
            this.iconActive = false;
          },
          tabChange(indexOfContent) {
            this.isActives.forEach((item, index) => {
              index === indexOfContent ?
                this.$set(this.isActives, index, true) :
                this.$set(this.isActives, index, false);
            })
          },
          publishNewWeiboContent() {
            if(!this.newWeiboContent.content) {
              alert('请输入内容!')
              return;
            }
            const newWeibo = JSON.parse(JSON.stringify(this.newWeiboContent));
            this.weiboNews.unshift(newWeibo);
            this.newWeiboContent.content = '';
            this.profileData[2].num++;
          },
          attention(index) {
            this.weiboNews[index].attention = true;    
            this.profileData[0].num++;    
          },
          unAttention(index) {
            this.weiboNews[index].attention = false;
            this.profileData[0].num--;
          },
          handleCollect(index) {
            this.weiboNews[index].collect = !this.weiboNews[index].collect;
            this.weiboNews[index].collect ? 
            this.collectNum++ :
            this.collectNum--;
          },
          handleLike(index) {
            this.weiboNews[index].like = !this.weiboNews[index].like;
            this.weiboNews[index].like ? 
            this.likeNum++ :
            this.likeNum--;
          }
        }
      }
    </script>
    
    <style lang="less">
      //css部分略
    </style>
    

    四. 项目单元测试脚本实战

    我们将以上文提到的"项目中的交互逻辑和需求"为基础, 为SinaWeibo.vue编写测试脚本, 下面我将展示测试用例编写过程:

    1.在文本框中输入内容后点击"发布"按钮(1), 会新发布内容到微博列表中, 且个人头像等下的微博数量(6)会增加1个

    it('点击发布按钮,发布新内容&个人微博数量增加1个', () => {
        const wrapper = mount(SinaWeibo);
        const textArea = wrapper.find('.weibo-publish-wrapper textarea');
        const buttonOfPublish = wrapper.find('.weibo-publish-wrapper button');
        const lengthOfWeiboNews = wrapper.vm.weiboNews.length;
        const countOfMyWeibo = wrapper.vm.profileData[2].num;
        
        //设置textArea的绑定数据
        wrapper.setData({newWeiboContent: {
          imgUrl: '../../static/image/profile.jpg',
          name: 'Lee_tanghui',
          resource: '刚刚 来自 网页版微博',
          content: '欢迎来到我的微博', 
          images: []
        }});
        //触发点击事件
        buttonOfPublish.trigger('click');
        const lengthOfWeiboNewsAfterPublish = wrapper.vm.weiboNews.length;
        const countOfMyWeiboAfterPublish = wrapper.vm.profileData[2].num;
    
        //断言: 发布新内容
        expect(lengthOfWeiboNewsAfterPublish).to.equal(lengthOfWeiboNews + 1);
        //断言: 个人微博数量增加1个
        expect(countOfMyWeiboAfterPublish).to.equal(countOfMyWeibo + 1);
        
      })
    

    测试结果:

    通过测试通过测试

    2.当文本框中无内容时, 不能发布空微博到微博列表, 且弹出提示框, 叫用户输入内容

     it('当文本框中无内容时, 不能发布空微博到微博列表, 且弹出提示框', () => {
        const wrapper = mount(SinaWeibo);
        const textArea = wrapper.find('.weibo-publish-wrapper textarea');
        const buttonOfPublish = wrapper.find('.weibo-publish-wrapper button');
        const lengthOfWeiboNews = wrapper.vm.weiboNews.length;
        const countOfMyWeibo = wrapper.vm.profileData[2].num;
        
        //设置textArea的绑定数据为空
        wrapper.setData({newWeiboContent: {
          imgUrl: '../../static/image/profile.jpg',
          name: 'Lee_tanghui',
          resource: '刚刚 来自 网页版微博',
          content: '', 
          images: []
        }});
        //触发点击事件
        buttonOfPublish.trigger('click');
        const lengthOfWeiboNewsAfterPublish = wrapper.vm.weiboNews.length;
        const countOfMyWeiboAfterPublish = wrapper.vm.profileData[2].num;
    
        //断言: 没有发布新内容
        expect(lengthOfWeiboNewsAfterPublish).to.equal(lengthOfWeiboNews);
        //断言: 个人微博数量不变
        expect(countOfMyWeiboAfterPublish).to.equal(countOfMyWeibo);
      })
    

    测试结果:

    image_1c2l52h0i11e51fjqm9o8751m7hm.png-32.4kBimage_1c2l52h0i11e51fjqm9o8751m7hm.png-32.4kB

    3.当点击"关注"(2), 个人头像下关注的数量(5)会增加1个, 且按钮内字体变成"取消关注"; 当点击"取消关注"(2), 个人头像下的数量(5)会减少1个, 且按钮内字体变成"关注"

      it('当点击"关注", 个人头像下关注的数量会增加1个, 且按钮内字体变成"取消关注"', () => {
        const wrapper = mount(SinaWeibo);
        const buttonOfAddAttendion = wrapper.find('.add');
        const countOfMyAttention = wrapper.vm.profileData[0].num;
      
        //触发事件
        buttonOfAddAttendion.trigger('click');
        
        const countOfMyAttentionAfterClick = wrapper.vm.profileData[0].num;
    
        //断言: 个人头像下关注的数量会增加1个
        expect(countOfMyAttentionAfterClick).to.equal(countOfMyAttention + 1);
        //断言: 按钮内字体变成"取消关注
        expect(buttonOfAddAttendion.text()).to.equal('取消关注');
      })
    
      it('当点击"取消关注", 个人头像下关注的数量会减少1个, 且按钮内字体变成"关注"', () => {
        const wrapper = mount(SinaWeibo);
        const buttonOfUnAttendion = wrapper.find('.cancel');
        const countOfMyAttention = wrapper.vm.profileData[0].num;
      
        //触发事件
        buttonOfUnAttendion.trigger('click');
        
        const countOfMyAttentionAfterClick = wrapper.vm.profileData[0].num;
    
        //断言: 个人头像下关注的数量会增加1个
        expect(countOfMyAttentionAfterClick).to.equal(countOfMyAttention - 1);
        //断言: 按钮内字体变成"取消关注
        expect(buttonOfUnAttendion.text()).to.equal('关注');
      })
    

    测试结果:

    image_1c2lbcvod1d7ton71mod1i6b1bt13.png-62.3kBimage_1c2lbcvod1d7ton71mod1i6b1bt13.png-62.3kB

    4.当点击"收藏"(3)时, 我的收藏(7)会增加1个数量, 且按钮内文字变成"已收藏"; 点击"已收藏"(3)时, 我的收藏(7)会减少1个数量, 且按钮内文字变成"收藏"

      it('当点击"收藏"时, 我的收藏会增加1个数量, 且按钮内文字变成"已收藏"', () => {
        const wrapper = mount(SinaWeibo);
        const buttonOfCollect = wrapper.find('.collectWeibo');
        const countOfMyCollect = Number(wrapper.find('.collect-num span').text());
    
        //触发点击事件
        buttonOfCollect.trigger('click');
        const countOfMyCollectAfterClick = Number(wrapper.find('.collect-num span').text());
    
        //断言: 我的收藏数量会加1
        expect(countOfMyCollectAfterClick).to.equal(countOfMyCollect + 1);
        //断言: 按钮内文字变成已收藏
        expect(buttonOfCollect.text()).to.equal('已收藏');
      })
    
      it('当点击"已收藏"时, 我的收藏会减少1个数量, 且按钮内文字变成"收藏"', () => {
        const wrapper = mount(SinaWeibo);
        const buttonOfUnCollect = wrapper.find('.uncollectWeibo');
        const countOfMyCollect = Number(wrapper.find('.collect-num span').text());
    
        //触发点击事件
        buttonOfUnCollect.trigger('click');
        const countOfMyCollectAfterClick = Number(wrapper.find('.collect-num span').text());
    
        //断言: 我的收藏数量会减1
        expect(countOfMyCollectAfterClick).to.equal(countOfMyCollect - 1 );
        //断言: 按钮内文字变成已收藏
        expect(buttonOfUnCollect.text()).to.equal('收藏');
      })
    

    测试结果:

    image_1c2lbdfe9i5jrja10cc447rgj1g.png-88.6kBimage_1c2lbdfe9i5jrja10cc447rgj1g.png-88.6kB

    5.当点击"赞"(4), 我的赞(8)会增加1个数量, 且按钮内文字变成"取消赞"; 点击"取消赞"(3)时, 我的赞(8)会减少1个数量, 且按钮内文字变成"赞"

      it('当点击"赞", 我的赞会增加1个数量, 且按钮内文字变成"取消赞"', () => {
        const wrapper = mount(SinaWeibo);
        const buttonOfLike = wrapper.find('.dislikedWeibo');
        const countOfMyLike = Number(wrapper.find('.like-num span').text());
    
        //触发点击事件
        buttonOfLike.trigger('click');
        const countOfMyLikeAfterClick = Number(wrapper.find('.like-num span').text());
    
        //断言: 我的赞会增加1个数量
        expect(countOfMyLikeAfterClick).to.equal(countOfMyLike + 1);
        //断言: 按钮内文字变成取消赞
        expect(buttonOfLike.text()).to.equal('取消赞');
      });
    
    it('当点击"取消赞", 我的赞会减少1个数量, 且按钮内文字变成"赞"', () => {
        const wrapper = mount(SinaWeibo);
        const buttonOfDislike = wrapper.find('.likedWeibo');
        const countOfMyLike = Number(wrapper.find('.like-num span').text());
    
        //触发点击事件
        buttonOfDislike.trigger('click');
        const countOfMyLikeAfterClick = Number(wrapper.find('.like-num span').text());
    
        //断言: 我的赞会增加1个数量
        expect(countOfMyLikeAfterClick).to.equal(countOfMyLike - 1);
        //断言: 按钮内文字变成取消赞
        expect(buttonOfDislike.text()).to.equal('赞');
      });
    

    测试结果

    image_1c2lbdt271iqs1tv34va1jlv5cb1t.png-151.9kBimage_1c2lbdt271iqs1tv34va1jlv5cb1t.png-151.9kB

    项目地址:

    Git仓库: https://github.com/Lee-Tanghui/Vue-Testing-Demo

    参考文章

    1. 测试框架 Mocha 实例教程 - 阮一峰
    2. Chai.js断言库API中文文档
    3. 知乎: 如果对vue进行单元测试
    4. Vue.js学习系列六——Vue单元测试Karma+Mocha学习笔记

    相关文章

      网友评论

        本文标题:Vue单元测试实战教程(Mocha/Karma + Vue-Te

        本文链接:https://www.haomeiwen.com/subject/mixagxtx.html