5.2 简单轮子:VueTab 组件单元测试

大纲链接 §

[toc]


保证组件的可测试性

VueTab.vue系列组件的单元测试

  • VueTab的单元测试tab.spec.ts
  • VueTabNav的单元测试tab-nav.spec.ts
  • VueTabItem的单元测试tab-item.spec.ts
  • VueTabContent的单元测试tab-content.spec.ts
  • VueTabPane的单元测试tab-pane.spec.ts

VueTab的单元测试tab.spec.ts

  • this.$children只能获取到子组件,不能获取到子元素
  • 单元测试难点 构造子元素或子组件
  • 用户事件为异步执行,需要使用异步测试 带上参数done
  • 二分log法:console.log('1')console.log('2')查看执行顺序,找到未执行的log
    • 打出未执行行代码的变量查看
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import Vue from 'vue';
import chai from 'chai';
// import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import VueTab from '../../src/components/tabs/VueTab.vue';
// import VueTabNav from '../../src/components/tabs/VueTabNav.vue';
// import VueTabContent from '../../src/components/tabs/VueTabContent.vue';
// import VueTabItem from '../../src/components/tabs/VueTabItem.vue';
// import VueTabPane from '../../src/components/tabs/VueTabPane.vue';
import {createTestVM, destroyVM} from '../testUtil';

chai.use(sinonChai);
const expect = chai.expect;

Vue.config.productionTip = false;
Vue.config.devtools = false;

describe('VueTab', () => {
  let vm: Vue;

  it('VueTab存在.', () => {
    expect(VueTab).to.exist;
  });

  describe('props', () => {
    afterEach(() => {
      destroyVM(vm);
    });

    it('VueTab可以接受selected', (done) => {
      vm = createTestVM(VueTab, {
        propsData: {
          selected: 'tab2',
          propsToNav: [
            {tabName: 'tab1'},
            {tabName: 'tab2'}
          ],
          propsToContent: [
            {paneName: 'pane1'},
            {paneName: 'pane2'}
          ]
        },
      }, true);

      setTimeout(() => {
        const selectedTab = vm.$el.querySelector('.tab-item:nth-child(1)');
        expect(selectedTab!.classList.contains('active')).to.eq(true);
        expect(selectedTab!.classList.contains('active')).to.be.true;
        done();
      }, 300);

    });

  });

});

  • 使用vm.$el.querySelector('.tab-item:nth-child(1)');按照顺序选出的节点不可靠,需要更加可测试的组件代码:
    • VueTabItem.vue组件标签中绑定自定义属性:data-name="tagName"
    • 可以直接根据用户传入的tagName属性查找测试中的节点

VueTabItem.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<template>
  <section
    class="tab-item"
    @click="emitSelectedVMtoEventBus"
    :class="classes"
    :data-name="tabName">
    <slot></slot>
  </section>
</template>

<script lang="ts">
import {Component, Inject, Prop, Vue} from 'vue-property-decorator';

@Component
export default class VueTabItem extends Vue {
  name = 'VueTabItem';
  active = false;
  @Prop({type: Boolean, default: false}) disabled!: boolean;

  @Inject('eventBus') readonly eventBus!: Vue;

  @Prop({type: String, required: false, default: ''}) tabName!: string;

  emitSelectedVMtoEventBus() {
    if (this.disabled) {return;}
    this.eventBus.$emit('update:selected', this.tabName, this);
  }

  get classes() {
    return {
      active: this.active,
      disabled: this.disabled
    };
  }

  onChangeActiveTag() {
    this?.eventBus?.$on('update:selected', (name: string) => {
      this.active = (name === this.tabName);
    });
  }

  created() {
    this.onChangeActiveTag();
  }


}
</script>
...

tab.spec.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import Vue from 'vue';
import chai from 'chai';
// import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import VueTab from '../../src/components/tabs/VueTab.vue';
import {createTestVM, destroyVM} from '../testUtil';

chai.use(sinonChai);
const expect = chai.expect;

Vue.config.productionTip = false;
Vue.config.devtools = false;

describe('VueTab', () => {
  let vm: Vue;

  it('VueTab存在.', () => {
    expect(VueTab).to.exist;
  });

  describe('props', () => {
    afterEach(() => {
      destroyVM(vm);
    });

    it('VueTab可以接受selected', (done) => {
      vm = createTestVM(VueTab, {
        propsData: {
          selected: 'tab2',
          propsToNav: [
            {tabName: 'tab1'},
            {tabName: 'tab2'}
          ],
          propsToContent: [
            {paneName: 'pane1'},
            {paneName: 'pane2'}
          ]
        },
      }, true);
      setTimeout(() => {
        // const selectedTab = vm.$el.querySelector('.tab-item:nth-child(1)');
        const selectedTab = vm.$el.querySelector(`.tab-item[data-name="tab2"]`);
        // expect(selectedTab!.classList.contains('active')).to.eq(true);
        expect(selectedTab!.classList.contains('active')).to.be.true;
        done();
      }, 300);

    });

  });

});

  • const selectedTab = vm.$el.querySelector(`.tab-item[data-name="tab2"]`)
  • 更精确的选择到.tab-item[data-name="tab2"]
  • 这样做的目的是编写可测试的代码