vue.js
jest
Vuex
vue-router
vue-cli

vue.jsとvue-routeを組み合わせてテストする際はまったこと

vue.js+vue-router+veuxのテストをjestで行う際warnが出てしまったので解決したメモ
普通にテストコードの意味を理解してればそんなに難しいことではない。

環境

"vue": "^2.5.2",
"vue-router": "^3.0.1",
"vuex": "^3.0.1"
"jest": "^21.2.1",

※vue-cliで作った。下記参照
vue.jsでテスト環境の構築からCI,コードカバレッジの設定まで

状況

以下のようなコードがあるとする

main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})
App.vue
<template>
  <div id="app">
    <img src="./assets/image/logo.png">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'app'
}
</script>

そんなに難しいことはしていない

  • まずmain.jsがよまれる。ここでは<div id=app></div><App />に置き換えている
  • 中身はApp.vueに定義されている
  • ここで重要なのはvuexとvue-routerの内容をrootであるmain.jsで読み込んでいること。
  • router-viewはrouterで定義された内容に基づいてアクセスしたパスで表示するコンポーネントを変える
  • 今回の場合は/にアクセスするとHelloWorld.vueが呼ばれる

テスト対象

components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <button v-on:click="add">{{add_button}}</button>
    <button v-on:click="sub">{{sub_button}}</button>
    <counter></counter>
  </div>
</template>

<script>
import Counter from './modules/Counter'
import { mapActions } from 'vuex'

export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      add_button: '+1',
      sub_button: '-1'
    }
  },
  components: {
    Counter
  },
  methods: mapActions({
    add: 'increment',
    sub: 'decreasement'
  })
}
</script>
  • コードの詳細には立ち入らないがvuexを使い簡単なカウンターを実装している
  • +ボタンを押すとCounterの中に描画される数字がプラスされ-だと逆の挙動をする。

テストコード

HelloWorld.spec.js
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld'

describe('HelloWorld.vue', () => {  
  it('should render correct contents', () => {
    # HelloWorldコンポーネントを読みこみVueのサブクラスを定義
    const Constructor = Vue.extend(HelloWorld)
    # このサブクラスを使いインスタンス化しマウントしている
    # マウントの詳細はここhttps://jp.vuejs.org/v2/api/index.html#vm-mount
    const vm = new Constructor().$mount()
    expect(vm.$el.querySelector('.hello h1').textContent)
    .toEqual('Welcome to Your Vue.js App')
  })
})
  • vue-cliでのデフォルトのテストコードになる
  • テストの内容はコメント参照

実行

npm run unit

  console.error node_modules/vue/dist/vue.runtime.common.js:576
    [Vue warn]: Unknown custom element: <router-link> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

    (found in <Root>)

  console.error node_modules/vue/dist/vue.runtime.common.js:576
    [Vue warn]: Error in render: "TypeError: Cannot read property 'getters' of undefined"

    found in

    ---> <Counter>
           <Root>

  console.error node_modules/vue/dist/vue.runtime.common.js:1715
    TypeError: Cannot read property 'getters' of undefined
以下似たようなのが流れる。。。

原因

  • テストではHelloworld.vueを読み込んでテストを行っている。
  • ここではvuexやvue-routerは読み込まれていない、なぜなら実際のコードではその上位で既に読み込まれているから
  • そのためテストコードがvuexやvue-routerでの処理を理解できずにエラーをはいている

修正

下記のようにインスタンス化するときにvuexとvue-routerの設定を読み込んでやればいい

HelloWorld.spec.js
 import Vue from 'vue'
+import router from '@/router'
+import store from '@/store'
 import HelloWorld from '@/components/HelloWorld'

 describe('HelloWorld.vue', () => {
   it('should render correct contents', () => {
     const Constructor = Vue.extend(HelloWorld)
-    const vm = new Constructor().$mount()
+    const vm = new Constructor({router, store}).$mount()
     expect(vm.$el.querySelector('.hello h1').textContent)
     .toEqual('Welcome to Your Vue.js App')

後書き

  • せっかくrootで読み込んで各コンポーネントに定義しないでよくなったのにテストはそうしないといけないのが気になる
  • 自分のやり方が悪いだけかも。いい方法があったらコメントで指摘ください。
  • vue-cliのデフォルトでできる環境がjestなのでそれを使っているが今の流行りは何なんだろう