使用Vue-test-utils测试Vue组件使用TypeScript创建

问题描述

我正在尝试使用 Vue-test-utils 包在Vue组件(使用TypeScript创建)中测试具有多种数据类型的道具。我尝试使用expect().tobe()使用,但仍然存在未发现的行:

DropDownList.vue

<template>
  <v-select
    :id="dropDownListID"
    :label="labeldisplay"
    :placeholder="getPlaceHolder"
    :item-text="itemtext"
    :item-value="itemvalue"
    :items="items"
    :value="value"
    @input="handleInput"
    :required="required"
    :rules="[required ? rules.required :false,additionalRulesFormated]"
    :class="{ 'roboto10':true,'xddl':true,['xddl-'+size] :true}"
    :return-object="returnobjectvalue"
    append-icon="expand_more"
    :disabled="disabled"
    :clearable="clearableValue"
  >
    <template slot="item" slot-scope="data">
      <v-flex>{{ data.item[itemtext] }}</v-flex>
    </template>
    <template slot="selection" slot-scope="data">
      <v-flex xs2 v-if="itemicon">
        <v-icon color="#3F3F3F" size="25px">{{ data.item[itemicon] }}</v-icon>
      </v-flex>
      <v-flex>{{ data.item[itemtext] }}</v-flex>
    </template>
  </v-select>
</template>

<script lang="ts">
import Vue from "vue";
import { logger } from "@/app/helpers/Logger";

export default Vue.extend({
  name: "x-drop-down-list",data: function() {
    return {
      rules: {
        required: (value: any) => {
          let label = (this as any).getRulesLabel;
          return !!value || label + " is required";
        }
      }
    };
  },props: {
    id: String,value: [String,Number,Boolean,Object,Array],size: String,label: String,placeholder: String,required: Boolean,itemtext: String,itemvalue: String,itemicon: String,items: Array,returnobject: String,ruleslabel: String || null,disabled: Boolean,additionalRules: [String,Boolean] || null,clearable: Boolean
  },computed: {
    dropDownListID: function() {
      let lbl = String(this.label === undefined || this.label === null ? null : String(this.label).replace(/ /g,""));
      let id = String(this.id === undefined || this.id === null ? lbl : this.id);
      return id;
    },returnobjectvalue: function() {
      let ret = this.returnobject === undefined || this.returnobject === null || this.returnobject === "" ? false : String(this.returnobject).toLowerCase() == "true" ? true : false;
      return ret;
    },getRulesLabel: function() {
      let id = this.label || this.id;
      let c = this.ruleslabel == undefined || this.ruleslabel == null ? id : this.ruleslabel;
      return c;
    },getPlaceHolder: function() {
      if (this.placeholder === undefined || this.placeholder === null || this.placeholder === this.label) return " ";

       let lbl = this.label == undefined || this.label == null ? "" : String(this.label).replace(" *","");
      let c = this.placeholder == undefined || this.placeholder == null ? lbl : this.placeholder;
      return c;
    },labeldisplay: function() {
      return (this.label || "") != "" ? this.label + (this.required ? " *" : "") : "";
    },additionalRulesFormated: function() {
      return String(this.additionalRules || "") != "" ? this.additionalRules : false;
    },clearableValue: function() {
      return this.clearable === undefined || this.clearable === null ? true : this.clearable;
    }
  },methods: {
    handleInput: function(val: string) {
      this.$emit("input",val);
    }
  }
});
</script>

<style lang="scss">
</style>

dropdownlist.spec.ts

/**
 * Unit test for component DropdownList.vue.
 */
import Vue from "vue";
import Vuetify from "vuetify";
import DropdownList from "@/components/dropdown/DropdownList.vue";
import { createLocalVue,shallowMount,Wrapper } from "@vue/test-utils";

describe("DropdownList.vue",() => {
  let vuetify: any;
  let mountFunction: (options?: object) => Wrapper<Vue>;
  const localVue = createLocalVue();
  Vue.use(Vuetify);

  const id = "dropdownlistcomponentid";
 
  beforeEach(() => {
    vuetify = new Vuetify();
    mountFunction = (options = {}) => {
      return shallowMount(DropdownList,{ localVue,vuetify,...options });
    };
  });

  it("props.ruleslabel as string || null",() => {
    var mywrapper = mountFunction({ 
      propsData: { 
        id: id,ruleslabel: "ruleslabel" || null 
      } });
    
    expect(mywrapper.vm.$props.ruleslabel).toBe("ruleslabel" || null);
  });
});

要测试道具:ruleslabeladditionalRules,我跑了npm run test:unit。测试通过了!,但是第59行仍然是未覆盖的行:

enter image description here

解决方法

String || null实际上是一个从short-circuitsString条件表达式,因为String构造函数是真实的,因此|| null具有这里没有有意义的值,应将其删除以避免被计为需要代码覆盖的表达式。同样,应从|| null中删除additionalRules,并在测试"ruleslabel"时将其删除。

我认为您可能一直在尝试指定可为空的string,如下所示:

let ruleslabel: string | null

...但是在这种情况下,props声明是构造函数的字符串的字典(不是类型)。 String构造函数,而string | null类型

指定此prop类型的正确方法是使用PropType

import Vue,{ PropType } from 'vue'

export default Vue.extend({
  props: {
    ruleslabel: String as PropType<string | null>,}
})

但是,prop已经是可选的,因此无需指定它可以为null。此外,考虑到x === undefined || x === null等效于!x,可以简化getRulesLabel()以消除对{{1}中的null(或undefined)的显式检查。 }:

ruleslabel

下面的示例可以简化代码中的许多其他条件。

,

要测试“ falsy”分支,当ruleslabel为falsy时,您至少需要测试一次。 在测试中,您使用ruleslabel: "ruleslabel" || null,这里的表达式"ruleslabel" || null将始终是真实的。

您可以这样测试:

it("works when props.ruleslabel is a string",() => {
    ...
    ruleslabel: "ruleslabel"
    ...

it("also works when props.ruleslabel is falsy",() => {
    ...
    ruleslabel: false
    ...

使用||运算符时,如果第一个值为true, 第二个值将被忽略,因此对于所有意图和目的,这两个表达式是相等的:

ruleslabel: "ruleslabel" || null
ruleslabel: "ruleslabel"

每当您看到guaranteedTruthy || anythingElse时,您就会知道其他任何东西都不会被使用。在javascript中,所有字符串都是真实的,除了空的一个“”。