使用Karma进行Vue组件测试:' undefined不是一个对象'

时间:2016-12-18 17:50:00

标签: unit-testing vue.js

我正在开发一个使用Vue loader's webpack template创建的应用。

我在创建项目时将Karma作为一个选项进行了测试,因此它已全部设置完毕并且我没有更改任何配置。

该应用程序是一个Github用户查找,目前包含三个组件; App.vueStats.vueUserForm.vue。统计信息和表单组件是包含应用程序组件的子项。

以下是App.vue

<template>
  <div id="app">
    <user-form
      v-model="inputValue"
      @go="submit"
      :input-value="inputValue"
    ></user-form>
    <stats
      :username="username"
      :avatar="avatar"
      :fave-lang="faveLang"
      :followers="followers"
    ></stats>
  </div>
</template>

<script>
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import _ from 'lodash'
import UserForm from './components/UserForm'
import Stats from './components/Stats'

Vue.use(VueAxios, axios)

export default {
  name: 'app',

  components: {
    UserForm,
    Stats
  },

  data () {
    return {
      inputValue: '',
      username: '',
      avatar: '',
      followers: [],
      faveLang: '',
      urlBase: 'https://api.github.com/users'
    }
  },

  methods: {
    submit () {
      if (this.inputValue) {
        const api = `${this.urlBase}/${this.inputValue}`

        this.fetchUser(api)
      }
    },

    fetchUser (api) {
      Vue.axios.get(api).then((response) => {
        const { data } = response

        this.inputValue = ''
        this.username = data.login
        this.avatar = data.avatar_url

        this.fetchFollowers()
        this.fetchFaveLang()
      }).catch(error => {
        console.warn('ERROR:', error)
      })
    },

    fetchFollowers () {
      Vue.axios.get(`${this.urlBase}/${this.username}/followers`).then(followersResponse => {
        this.followers = followersResponse.data.map(follower => {
          return follower.login
        })
      })
    },

    fetchFaveLang () {
      Vue.axios.get(`${this.urlBase}/${this.username}/repos`).then(reposResponse => {
        const langs = reposResponse.data.map(repo => {
          return repo.language
        })

        // Get most commonly occurring string from array
        const faveLang = _.chain(langs).countBy().toPairs().maxBy(_.last).head().value()
        if (faveLang !== 'null') {
          this.faveLang = faveLang
        } else {
          this.faveLang = ''
        }
      })
    }
  }
}
</script>

<style lang="stylus">
body
  background-color goldenrod

</style>

以下是Stats.vue

<template>
  <div class="container">
    <h1 class="username" v-if="username">{{username}}</h1>
    <img v-if="avatar" :src="avatar" class="avatar">
    <h2 v-if="faveLang">Favourite Language: {{faveLang}}</h2>
    <h3 v-if="followers.length > 0">Followers ({{followers.length}}):</h3>
    <ul v-if="followers.length > 0">
      <li v-for="follower in followers">
        {{follower}}
      </li>
    </ul>
  </div>
</template>

<script>
  export default {
    name: 'stats',
    props: [
      'username',
      'avatar',
      'faveLang',
      'followers'
    ]
  }
</script>

<style lang="stylus" scoped>
h1
  font-size 44px

.avatar
  height 200px
  width 200px
  border-radius 10%

.container
  display flex
  align-items center
  flex-flow column
  font-family Comic Sans MS

</style>

这是UserForm.vue

<template>
  <form @submit.prevent="handleSubmit">
    <input
      class="input"
      :value="inputValue"
      @input="updateValue($event.target.value)"
      type="text"
      placeholder="Enter a GitHub username..."
    >
    <button class="button">Go!</button>
  </form>
</template>

<script>
export default {
  props: ['inputValue'],
  name: 'user-form',
  methods: {
    updateValue (value) {
      this.$emit('input', value)
    },
    handleSubmit () {
      this.$emit('go')
    }
  }
}
</script>

<style lang="stylus" scoped>

input
  width 320px

input,
button
  font-size 25px

form
  display flex
  justify-content center

</style>

我为UserForm.vue编写了一个简单的测试,测试<button>的外部HTML:

import Vue from 'vue'
import UserForm from 'src/components/UserForm'

describe('UserForm.vue', () => {
  it('should have a data-attribute in the button outerHTML', () => {
    const vm = new Vue({
      el: document.createElement('div'),
      render: (h) => h(UserForm)
    })
    expect(vm.$el.querySelector('.button').outerHTML)
      .to.include('data-v')
  })
})

这很好用;运行npm run unit时的输出是:

   UserForm.vue
    ✓ should have a data-attribute in the button outerHTML

但是,当我尝试根据the documentationStats.vue编写类似的简单测试时,我遇到了问题。

以下是测试:

import Vue from 'vue'
import Stats from 'src/components/Stats'

// Inspect the generated HTML after a state update
it('updates the rendered message when vm.message updates', done => {
  const vm = new Vue(Stats).$mount()
  vm.username = 'foo'
  // wait a "tick" after state change before asserting DOM updates
  Vue.nextTick(() => {
    expect(vm.$el.querySelector('.username').textContent).toBe('foo')
    done()
  })
})

以下是运行npm run unit时的相应错误:

ERROR LOG: '[Vue warn]: Error when rendering root instance: '
  ✗ updates the rendered message when vm.message updates
        undefined is not an object (evaluating '_vm.followers.length')

我尝试了以下尝试以使测试正常运行:

  • 更改vm测试中Stats创建的方式与UserForm测试相同 - 返回相同的错误
  • 测试组件的各个部分,例如组件中div的textContent - 返回相同的错误

为什么错误是指_vm.followers.length?前面有下划线的_vm是什么?如何解决此问题才能成功测试我的组件?

(包含所有代码的回复:https://github.com/alanbuchanan/vue-github-lookup-2

1 个答案:

答案 0 :(得分:1)

  

为什么错误是指_vm.followers.length?前面有下划线的_vm是什么?

这段代码来自Vue将您的模板编译成的渲染功能。 _vm是一个占位符,当vue-loader在构建期间将模板转换为渲染函数时,它会自动插入到所有Javascript表达式中 - 它可以提供对组件的访问。

在模板中执行此操作时:

{{followers.length}}

这段代码的render函数中的编译结果将是:

_vm.followers.length

现在,为什么错误发生在第一位?因为您已在组件上定义了道具followers,但没有为其提供任何数据 - 因此,道具的值为undefined

解决方案:要么为prop提供默认值:

// Stats.vue
props: {
  followers: { default: () => [] }, // function required to return fresh object
  // ... other props
}

或者你为道具提供了实际值:

// in the test:
const vm = new Vue({
  ...Stats,
  propsData: {
    followers: [/* ... actual data*/]
  }
}).$mount()