数ヶ月前、私はたくさんのJavaScriptを書く必要のあるプロジェクトに取り組み始めました。 私がますます多くのコードを書いているにつれて、単体テストの欠如が私を遅くしていることが明らかになりました。 最新の機能で問題が発生したかどうかを確認する方法がなく、すべてを手動で確認するのが面倒になりました。

その時点からテストが私の最優先事項であることに気づいたので、私は読んで学び始めました。 いくつかの選択肢は自然に生まれました。 Karma Mocha Chai Sinon.JS は素晴らしいバンドルであり、必要なものがすべて揃っています。

API、 Vuex 、およびソケットと相互作用するコンポーネントのテストに関しては、まだ問題がありました。 VueコンポーネントとVuexストア間の相互作用のテストで特定の問題が発生しました。 そこで、同じ状況で誰かに役立つ可能性のある簡単な例を書くことにしました。

出発点

この投稿のVueを設定するには、vue-cliを使用します。 プロジェクトの足場となり、最も重要なこととして、テスト環境全体を構成します。 Vue-cli は、必要なセットアップの種類についてプロンプトを表示します。 この投稿の目的上、NightWatchを使用してエンドツーエンドのテストを設定したくなかったので、今すぐスキップできますが、これは間違いなく調査する必要があります。

$ npm install -g vue-cli
$ vue init webpack my-project
$ cd my-project
$ npm install

この後、プロジェクトを実行して、すべてが機能しているかどうかを確認できます。

$ npm run dev

また、テストを実行して、すべてが期待どおりに機能していることを確認することをお勧めします。

$ npm test run

Vuexのインストール

公式ドキュメントから:「VuexはVue.jsアプリケーション用の状態管理パターン+ライブラリです。 これは、アプリケーション内のすべてのコンポーネントの集中ストアとして機能し、状態を予測可能な方法でのみ変更できるようにするルールを備えています。」

同じページにいくつかのVueコンポーネントがあり、それらが同じデータを変更する必要があるとします。 タスクのリスト、新しいタスクを作成して既存のタスクを変更するためのモーダルウィンドウ、および最後に変更された3つのタスクを表示するサイドバーがあります。

これらのコンポーネントは明らかに相互に通信する必要があり、子から親にイベントを発行したり、子に小道具を渡したり、Vueアプリインスタンスでデータを定義したりするなど、Vuexなしでこれを実現する方法がありますが、どちらにもいくつかの欠点があります。

ここでVuexが登場し、これらのタイプの問題に対する簡単で保守可能なソリューションを提供します。

これをインストールするには、プロジェクトのルートに移動して次のコマンドを実行します。

$ npm install --save vuex

PhantomJSを使用するデフォルトのvue-cli Karmaセットアップを使用するため、Vuexを機能させるには Promisepolyfillを使用する必要があります。

$ npm install --save-dev es6-promise

さて、それが邪魔にならないように、楽しんで、プロジェクトにVuexを含めましょう。 の中に src/ フォルダを追加します store 次のディレクトリ構造を持つフォルダ:

  • index.js(ストアのブートストラップ)
  • 突然変異-types.js
  • アクション.js
  • getters.js
  • api.js(apiサービス)
  • items.json(架空のAPI)

Vuexは1つのファイルで設定できますが、多くのミューテーションを処理していると混乱し、テストがより困難になります。 Vuexに慣れていない場合は、最初に公式ドキュメントを読むことをお勧めします。

src / store / index.js
require('es6-promise').polyfill()
import Vue from 'vue/dist/vue.common.js'
import Vuex from 'vuex/dist/vuex.js'

Vue.use(Vuex)

import * as types from './mutation-types'
import * as actions from './actions'
import * as getters from './getters'

const state = {
  items: []
}

const mutations = {
  [types.setItems] (state, items) {
    state.items = items
  }
}

const options = {
  state,
  mutations,
  actions,
  getters
}

export default new Vuex.Store(options)
export { options }
src / store / actions.js
import * as types from './mutation-types'
import api from './api'

export const setItems = ({commit}) => {
  api.getItems()
    .then((items) => {
      commit(types.setItems, items)
    })
    .catch((err) => console.log(err))
}
src / store / mutation-types.js
export const setItems = 'SET_ITEMS'
src / store / getters.js
export const items = state => {
  return state.items
}
src / store / api.js
export default {
  getItems: function () {
    return new Promise((resolve, reject) => {
      // imagine we're making an API request here
      const response = require('./items.json')
      resolve(response.body)
    })
  }
}
サーバーの応答:src / store / items.json
{
  "body": [
    "coffee",
    "sugar",
    "water"
  ]
}

基本的なVuexストアのセットアップが完了したので、それをVueアプリインスタンスに含める必要があります。

src / main.js
...
import store from './store'
...

new Vue({
  el: '#app',
  store, // uses our newly created store in our Vue instance
  template: '<App/>',
  components: { App }
})

Vuexの状態に依存するVueコンポーネント

Vuexストアをセットアップし、それをVueアプリインスタンスに含めたので、ストアの状態を使用する非常に単純なVueコンポーネントを作成しましょう。 新しいファイルを作成する src/components/Items.vue 次の内容で:

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index" class="items">
      {{ item }}
    </li>
  </ul>
</template>
<script>
  import { mapActions, mapGetters } from 'vuex'
  export default {
    mounted () {
      this.setItems()
    },
    methods: {
      ...mapActions(['setItems'])
    },
    computed: {
      ...mapGetters(['items'])
    }
  }
</script>

ご覧のとおり、ここで見るのは興味深いことではありません。 ストアのアイテムのリストをレンダリングしています。 マウントされたフックでは、APIからデータをフェッチし、それを状態に設定するアクションをディスパッチします。 MapActionsmapGettersは、アクションとゲッターをコンポーネントに直接挿入するのではなく、便利な方法です。 this.$store.dispatch(‘setItems’) また this.$store.state.items.

Items.vueコンポーネントの単体テストの作成

では、どのようにテストするのでしょうか? 明らかに、このコンポーネントはストアにアクセスできなければテストできません。 したがって、テストにストアを含める必要がありますが、APIについてはどうでしょうか。 そのままテストすることもできますが、変数が多すぎるため、テスト環境は無菌であり、毎回同じである必要があります。

ステップバイステップでそれを取りましょう。 新しいファイルを作成する test/unit/specs/Items.spec.js 次の内容で:

require('es6-promise').polyfill()
import Vue from 'vue/dist/vue.common.js'
import Vuex from 'vuex/dist/vuex.js'
import store from '../../../src/store'
import Items from '../../../src/components/Items.vue'

describe('Items.vue', () => {
  it('test initial rendering with api', (done) => {
    const vm = new Vue({
      template: '<div><test></test></div>',
      store,
      components: {
        'test': Items
      }
    }).$mount()

    Vue.nextTick()
      .then(() => {
        expect(vm.$el.querySelectorAll('.items').length).to.equal(3)
        done()
      })
      .catch(done)
  })
})

このテストでは、コンポーネントのみがテンプレートに含まれている新しいVueアプリインスタンスをマウントします。 <test></test>. アプリがマウントされた後、コンポーネントもマウントされ、マウントされたフックでVuexアクションとそれに続くAPIリクエストをディスパッチします。 を使用しております Vue.nextTick() DOMの更新は非同期であり、それを使用しなかった場合、その時点でDOMが更新されないため、テストに合格しませんでした。 APIはファイルから読み取っているだけなので、このテストは合格ですが、別の配列項目をに追加すると src/store/items.json 失敗します。

だから私たちがする必要があるのはモックアップです src/store/api.js サービス。 このために、inject-loaderを使用します。 したがって、最初に行う必要があるのは、それをインストールすることです。

$ npm install inject-loader@^2.0.0

これでインストールが完了したので、テストを書き直して、モックAPIサービスをに注入できます。 src/store/actions.js. 変更しましょう test/unit/specs/Items.spec.js 以下を含むファイル:

require('es6-promise').polyfill()
import Vue from 'vue/dist/vue.common.js'
import Vuex from 'vuex/dist/vuex.js'
import Items from '../../../src/components/Items.vue'
import * as types from '../../../src/store/mutation-types'
import * as getters from '../../../src/store/getters'

describe('Items.vue', () => {
  it('test initial rendering with mock data', (done) => {
    const actionsInjector = require('inject-loader!../../../src/store/actions')
    const actions = actionsInjector({
      './api': {
        getItems () {
          return new Promise((resolve, reject) => {
            const arr = ['Cat', 'Dog', 'Fish', 'Snail']
            resolve(arr)
          })
        }
      }
    })

    const state = {
      items: []
    }

    const mutations = {
      [types.setItems] (state, items) {
        state.items = items
      }
    }

    const options = {
      state,
      mutations,
      actions,
      getters
    }

    const mockStore = new Vuex.Store(options)

    const vm = new Vue({
      template: '<div><test></test></div>',
      store: mockStore,
      components: {
        'test': Items
      }
    }).$mount()

    Vue.nextTick()
      .then(() => {
        expect(vm.$el.querySelectorAll('.items').length).to.equal(4)
        done()
      })
      .catch(done)
  })
})

奇妙なインラインrequire in actions.js 基本的に私たちの本物を注入します actions.js ファイルですが、そのファイルの依存関係をスタブ化できます。 そのファイルの次の行は、actionsInjectorでそれがどのように行われるかを示しています。 APIオブジェクトを、テスト全体で一貫したデータを返す独自のオブジェクトに置き換えます。 これで、実行するだけです。

$ npm test run

そして、すべての緑の線をお楽しみください! 🎉🍕