ガイド

はじめる

セットアップ

vue-test-utils の使い方を体験したい場合は、基本設定としてデモリポジトリをクローンし、依存関係をインストールしてください。

git clone https://github.com/vuejs/vue-test-utils-getting-started
cd vue-test-utils-getting-started
npm install

プロジェクトには単純なコンポーネント、counter.js が含まれています。

// counter.js

export default {
  template: `
    <div>
      <span class="count">{{ count }}</span>
      <button @click="increment">Increment</button>
    </div>
  `,

  data () {
    return {
      count: 0
    }
  },

  methods: {
    increment () {
      this.count++
    }
  }
}

マウンティングコンポーネント

vue-test-utils は Vue コンポーネントを隔離してマウントし、必要な入力(プロパティ、注入、そしてユーザイベント)をモックし、そして出力(描画結果、カスタムイベントの発行)を検証することでテストします。

マウントされたコンポーネントは Wrapper の内部に返されます。これは、基の Vue コンポーネントインスタンスを操作、トラバース、クエリ処理するための多くの便利なメソッドを公開しています。

mount メソッドを使ってラッパを作成することができます。test.js というファイルを作りましょう:

// test.js

// test utils から mount() メソッドをインポート
// テストするコンポーネント
import { mount } from '@vue/test-utils'
import Counter from './counter'

// コンポーネントがマウントされ、ラッパが作成されます。
const wrapper = mount(Counter)

// wrapper.vmを 介して実際の Vue インスタンスにアクセスできます
const vm = wrapper.vm

// ラッパをより深く調べるためにコンソールに記録してみましょう。
// vue-test-utils でのあなたの冒険はここから始まります。
console.log(wrapper)

コンポーネントの描画された HTML 出力をテストする

ラッパが完成したので、コンポーネントの描画された HTML 出力が、期待されるものと一致することを確認します。

import { mount } from '@vue/test-utils'
import Counter from './counter'

describe('Counter', () => {
  // コンポーネントがマウントされ、ラッパが作成されます。
  const wrapper = mount(Counter)

  it('renders the correct markup', () => {
    expect(wrapper.html()).toContain('<span class="count">0</span>')
  })

  // 要素の存在を確認することも簡単です
  it('has a button', () => {
    expect(wrapper.contains('button')).toBe(true)
  })
})

次に、npm test でテストを実行します。テストが合格になるはずです。

ユーザのインタラクションをシミュレーションする

ユーザがボタンをクリックすると、カウンタがカウントをインクリメントする必要があります。この振る舞いをシミュレートするには、まずbutton 要素のラッパを返す wrapper.find() を使ってボタンを見つける必要があります。ボタンのラッパで .trigger() を呼び出すことでクリックをシミュレートできます:

it('button click should increment the count', () => {
  expect(wrapper.vm.count).toBe(0)
  const button = wrapper.find('button')
  button.trigger('click')
  expect(wrapper.vm.count).toBe(1)
})

nextTick はどうですか?

Vue は保留した DOM 更新をまとめて処理し、非同期に適用して、複数のデータのミューテーションに起因する不要な再描画を防ぎます。実際には、Vue が何らかの状態変更をトリガした後に Vue が実際の DOM 更新を実行するまで待つために、Vue.nextTick を使用しなければならないからです。

使い方を簡単にするため、 vue-test-utils はすべての更新を同期的に適用するので、テストで DOM を更新するために Vue.nextTick を使う必要はありません。

注意: 非同期コールバックやプロミスの解決などの操作のために、イベントループを明示的に進める必要がある場合は、まだ nextTick が必要です。

テストファイルで nextTick をまだ使う必要がある場合は、 nextTick の内部で Promise を使っているので、 nextTick 内で発生したエラーはテストランナーによって捕捉されないことに注意してください。これを解決するには2つの方法があります。 1つ目はテストの最初でVueのグローバルエラーハンドラに done コールバックをセットする方法です。2つ目は nextTick を引数なしで実行して、それを Promise としてテストランナーに返す方法です。

// これは捕捉されない
it('will time out', (done) => {
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})

// 以下の2つのテストは期待通り動作します
it('will catch the error using done', (done) => {
  Vue.config.errorHandler = done
  Vue.nextTick(() => {
    expect(true).toBe(false)
    done()
  })
})

it('will catch the error using a promise', () => {
  return Vue.nextTick()
    .then(function () {
      expect(true).toBe(false)
    })
})

次は何をするのか

一般的なヒント

何をテストするかを知る

UI コンポーネントでは、コンポーネントの内部実装の詳細に集中しすぎて脆弱なテストが発生する可能性があるため、完全なラインベースのカバレッジを目指すことはお勧めしません。

代わりに、コンポーネントのパブリックインターフェイスを検証するテストを作成し、内部をブラックボックスとして扱うことをお勧めします。単一のテストケースでは、コンポーネントに提供された入力(ユーザーのやり取りやプロパティの変更)によって、期待される出力(結果の描画またはカスタムイベントの出力)が行われることが示されます。

たとえば、ボタンがクリックされるたびに表示カウンタを 1 ずつインクリメントする Counter コンポーネントの場合、そのテストケースはクリックをシミュレートし、描画された出力が 1 つ増加したのか検証します。カウンタは値をインクリメントし、入力と出力のみを扱います。

このアプローチの利点は、コンポーネントのパブリックインターフェイスが同じままである限り、コンポーネントの内部実装が時間の経過とともにどのように変化してもテストは合格になります。

このトピックは、Matt O'Connell による偉大なプレゼンテーションで詳細に説明されています。

Shallow 描画

単体テストでは、通常、単体テストとしてテスト対象のコンポーネントに焦点を当て、子コンポーネントの動作を間接的に検証することを避けたいと考えています。

さらに、多くの子コンポーネントを含むコンポーネントの場合、描画されたツリー全体が非常に大きくなる可能性があります。すべての子コンポーネントを繰り返し描画すると、テストが遅くなる可能性があります。

vue-test-utils を使うと、shallowMount メソッドを使って子コンポーネントを(スタブによって)描画せずにコンポーネントをマウントすることができます:

import { shallowMount } from '@vue/test-utils'

const wrapper = shallowMount(Component) // Component インスタンスを含む Wrapper を返します。
wrapper.vm // マウントされた Vue インスタンス

イベントの発行を検証する

マウントされた各ラッパは、基になる Vue インスタンスによって発行されたすべてのイベントを自動的に記録します。wrapper.emitted() を使って、記録されたイベントを取り出すことができます:

wrapper.vm.$emit('foo')
wrapper.vm.$emit('foo', 123)

/*
wrapper.emitted()は次のオブジェクトを返します:
{
  foo: [[], [123]]
}
*/

次に、これらのデータに基づいて検証することもできます。

// イベントが発行されたか検証する
expect(wrapper.emitted().foo).toBeTruthy()

// イベント数を検証する
expect(wrapper.emitted().foo.length).toBe(2)

// イベントのペイロードを検証する
expect(wrapper.emitted().foo[1]).toEqual([123])

また、wrapper.emittedByOrder() を呼び出すことで、発行順序のイベントの配列を取得することもできます。

コンポーネントの状態を操作する

ラッパの setData メソッドまたは setProps メソッドを使って、コンポーネントの状態を直接操作することができます。:

wrapper.setData({ count: 10 })

wrapper.setProps({ foo: 'bar' })

プロパティをモックする

Vue に組み込まれた propsData オプションを使用してコンポーネントにプロパティを渡すことができます:

import { mount } from '@vue/test-utils'

mount(Component, {
  propsData: {
    aProp: 'some value'
  }
})

wrapper.setProps({}) メソッドを使用して、すでにマウントされているコンポーネントのプロパティを更新することもできます。

オプションの完全なリストについては、ドキュメントのマウントオプションのセクションを参照してください。

グローバルプラグインとミックスインの適用

コンポーネントの中には、グローバルプラグインやミックスインによって注入された機能 (例: vuexvue-router など)に依存するものもあります。

特定のアプリケーションでコンポーネントのテストを作成している場合は、同じグローバルプラグインとミックスインをテストのエントリに設定できます。しかし、異なるアプリケーション間で共有される可能性のあるジェネリックコンポーネントスイートをテストする場合など、グローバルな Vue コンストラクタを汚染することなく、より孤立した設定でコンポーネントをテストする方が良い場合もあります。createLocalVue メソッドを使用すると、次のことができます:

import { createLocalVue } from '@vue/test-utils'

// 拡張された Vue コンストラクタを作成する
const localVue = createLocalVue()

// プラグインをインストールする
localVue.use(MyPlugin)

// localVue をマウントオプションに渡す
mount(Component, {
  localVue
})

Vue Router のようなプラグインはグローバルの Vue コンストラクタに read-only なプロパティを追加します。
これは localVue コンストラクタにそのプラグインを再びインストールすることや read-only なプロパティに対するモックを追加することを不可能にします。

モックの注入

単純なモックを注入するための別の戦略として mocks オプションで行うことができます:

import { mount } from '@vue/test-utils'

const $route = {
  path: '/',
  hash: '',
  params: { id: '123' },
  query: { q: 'hello' }
}

mount(Component, {
  mocks: {
    $route // コンポーネントをマウントする前に、モックした $route オブジェクトを Vue インスタンスに追加します。
  }
})

ルーティングの扱い

定義によるルーティングは、アプリケーションの全体的な構造と関連し、複数のコンポーネントが関係するため、統合テストまたはエンドツーエンドテストによってよくテストされます。 vue-router 機能に依存する個々のコンポーネントについては、上記の手法を使ってモックすることができます。

キー、マウス、その他の DOM イベントのテスト

イベントをトリガする

Wrappertrigger メソッドで DOM イベントをトリガすることができます。

const wrapper = mount(MyButton)

wrapper.trigger('click')

find メソッドは mount メソッドと同じように Wrapper を返します。 MyComponent 内に button があると仮定すると、以下のコードは、 button をクリックします。

const wrapper = mount(MyComponent)

wrapper.find('button').trigger('click')

オプション

trigger メソッドはオプションで options オブジェクトを引数として取ります。options オブジェクトのプロパティはイベントオブジェクトのプロパティに追加されます。

target を options オブジェクトに追加することができないことに注意してください。

const wrapper = mount(MyButton)

wrapper.trigger('click', { button: 0 })

マウスクリックの例

テスト対象のコンポーネント

<template>
<div>
  <button class="yes" @click="callYes">Yes</button>
  <button class="no" @click="callNo">No</button>
</div>
</template>
<script>
export default {
  name: 'YesNoComponent',
  props: {
    callMe: {
      type: Function
    }
  },
  methods: {
    callYes() {
      this.callMe('yes')
    },
    callNo() {
      this.callMe('no')
    }
  }
}
</script>

テスト

import YesNoComponent from '@/components/YesNoComponent'
import { mount } from '@vue/test-utils'
import sinon from 'sinon'

describe('Click event', () => {
  it('Click on yes button calls our method with argument "yes"', () => {
    const spy = sinon.spy()
    const wrapper = mount(YesNoComponent, {
      propsData: {
        callMe: spy
      }
    })
    wrapper.find('button.yes').trigger('click')

    spy.should.have.been.calledWith('yes')
  })
})

キーボードの例

テスト対象のコンポーネント

このコンポーネントはいくつかのキーを使用して quantity を増減することができます。

<template>
<input type="text" @keydown.prevent="onKeydown" v-model="quantity" />
</template>
<script>
const KEY_DOWN = 40
const KEY_UP = 38
const ESCAPE = 27
const CHAR_A = 65

export default {
  data() {
    return {
      quantity: 0
    }
  },
  methods: {
    increment() {
      this.quantity += 1
    },
    decrement() {
      this.quantity -= 1
    },
    clear() {
      this.quantity = 0
    },
    onKeydown(e) {
      if (e.keyCode === ESCAPE) {
        this.clear()
      }
      if (e.keyCode === KEY_DOWN) {
        this.decrement()
      }
      if (e.keyCode === KEY_UP) {
        this.increment()
      }
      if (e.which === CHAR_A) {
        this.quantity = 13
      }
    }
  },
  watch: {
    quantity: function (newValue) {
      this.$emit('input', newValue)
    }
  }
}
</script>

テスト

import QuantityComponent from '@/components/QuantityComponent'
import { mount } from '@vue/test-utils'

describe('Key event tests', () => {
  it('Quantity is zero by default', () => {
    const wrapper = mount(QuantityComponent)
    expect(wrapper.vm.quantity).toBe(0)
  })

  it('Cursor up sets quantity to 1', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.trigger('keydown.up')
    expect(wrapper.vm.quantity).toBe(1)
  })

  it('Cursor down reduce quantity by 1', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.vm.quantity = 5
    wrapper.trigger('keydown.down')
    expect(wrapper.vm.quantity).toBe(4)
  })

  it('Escape sets quantity to 0', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.vm.quantity = 5
    wrapper.trigger('keydown.esc')
    expect(wrapper.vm.quantity).toBe(0)
  })

  it('Magic character "a" sets quantity to 13', () => {
    const wrapper = mount(QuantityComponent)
    wrapper.trigger('keydown', {
      which: 65
    })
    expect(wrapper.vm.quantity).toBe(13)
  })
})

制限事項

. の後のキー名( keydown.up の場合 up )は keyCode に変換されます。以下のキー名が変換されます。

キー名 キーコード
enter 13
esc 27
tab 9
space 32
delete 46
backspace 8
insert 45
up 38
down 40
left 37
right 39
end 35
home 36
pageup 33
pagedown 34

重要事項

vue-test-utils は同期的にイベントをトリガします。従って、 Vue.nextTick() を実行する必要はありません。

テストランナを選ぶ

テストランナは、テストを実行するプログラムです。

多くの一般的な JavaScript テストランナがあり、vue-test-utils はそれらすべてで動作します。テストランナにとらわれません。

ですが、テストランナを選択する際には、機能セット、パフォーマンス、および単一ファイルコンポーネント (SFC) の事前コンパイルのサポートなどを考慮すべきです。既存のライブラリを慎重に比較した上で、以下の2つのテストランナをお勧めします:

  • Jest は最も充実したテストランナです。最小の設定が必要で、デフォルトで JSDOM を設定し、組み込みの検証を提供し、コマンドラインのユーザーエクスペリエンスが優れています。ただし、テストで SFC コンポーネントをインポートできるようにするには、プリプロセッサが必要です。最も一般的な SFC 機能を処理できる vue-jest プリプロセッサを作成しましたが、現在 vue-loader と 100% 同じ機能を持っていません。

  • mocha-webpack は webpack + Mocha のラッパですが、より合理的なインタフェースと watch モードを備えています。この設定のメリットは、webpack + vue-loader を使用して完全な SFC サポートを得ることができるということですが、より多くの設定を行う必要があります。

ブラウザ環境

vue-test-utils はブラウザ環境に依存します。技術的には、実際のブラウザで実行することはできますが、異なるプラットフォーム上で実際のブラウザを起動するという複雑さのため、お勧めできません。代わりに、JSDOM を使用して仮想ブラウザ環境で Node.js でテストを実行することをお勧めします。

Jest テストランナーは JSDOM を自動的に設定します。他のテストランナーの場合は、jsdom-global を使用してテスト用の JSDOM を手動で設定できます:

npm install --save-dev jsdom jsdom-global

// テストのセットアップと登録
require('jsdom-global')()

単一ファイルコンポーネントをテストする

単一ファイルコンポーネントは、ノードまたはブラウザで実行する前に事前コンパイルが必要です。コンパイルを実行するには、Jest プリプロセッサを使用する方法と webpack を直接使用する方法が推奨されます。

vue-jest プリプロセッサは基本的な SFC 機能をサポートしていますが、現在 vue-loader でのみサポートされているスタイルブロックやカスタムブロックは扱いません。これらの機能やその他の Webpack 固有の設定に依存する場合は、webpack + vue-loader ベースの設定を使用する必要があります。

さまざまな設定については、次のガイドをお読みください:

リソース

Jest を使用した単一ファイルコンポーネントのテスト

このセットアップのサンプルプロジェクトは、 GitHub にあります。

Jest は Facebook が開発したテストランナであり、ユニットテストソリューションの提供を目指しています。 Jest の詳細については、公式ドキュメントを参照してください。

Jest のセットアップ

既に、webpack、vue-loader、および Babel が正しく設定されている設定から始めると仮定します。例: vue-cli によって雛形生成された webpack-simple テンプレートです。

まず Jest と vue-test-utils をインストールします:

$ npm install --save-dev jest @vue/test-utils

次に、package.json にスクリプトを定義する必要があります。

// package.json
{
  "scripts": {
    "test": "jest"
  }
}

Jest における単一ファイルコンポーネントの処理

Jest に *.vue ファイルの処理方法を教えるために、vue-jest プリプロセッサをインストールして設定する必要があります。:

npm install --save-dev vue-jest

次に、package.jsonjest ブロックを作成します:

{
  // ...
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      // *.vue ファイルを処理するように Jest に指示する
      "vue"
    ],
    "transform": {
      // vue-jest で *.vue ファイルを処理する
      ".*\\.(vue)$": "vue-jest"
    }
  }
}

注意: vue-jest は現在、カスタムブロックのサポートやスタイルのロードなど、vue-loader のすべての機能をサポートしていません。さらに、コード分割などのWebpack固有の機能はサポートされていません。サポートされていない機能を使用するには、 Jest の代わりに Mocha をテストランナーとして使用します。そして、 Webpack をコンポーネントをコンパイルするために使用します。やり方は Mocha + webpackによる単一ファイルコンポーネントのテストのガイドをお読みください。

Webpack エイリアスの処理

webpack 設定で、@/src のエイリアスにしたいといった場合、moduleNameMapperオプションを使って Jest の設定を追加する必要があります

{
  // ...
  "jest": {
    // ...
    // ソースコードにある @ を src へと割当てる
    "moduleNameMapper": {
      "^@/(.*)$": "<rootDir>/src/$1"
    }
  }
}

Jest のための Babel の設定

Node の最新バージョンではすでにほとんどの ES2015 機能がサポートされていますが、テストでは ES Modules 構文と stage-x 機能を使用することができます。そのために、babel-jest をインストールする必要があります。

npm install --save-dev babel-jest

次に、Jest に babel-jest で JavaScript テストファイルを処理するよう、package.jsonjest.transform の中にエントリを追加する必要があります:

{
  // ...
  "jest": {
    // ...
    "transform": {
      // ...
      // babel-jest で js を処理する
      "^.+\\.js$": "<rootDir>/node_modules/babel-jest"
    },
    // ...
  }
}

デフォルトでは、babel-jest はインストールしさえすれば自動的に設定します。しかし、*.vue ファイルのための変換を明示的に追加したため、babel-jest も明示的に設定する必要があります。

webpack で babel-preset-env を使用するとした場合、webpack は ES Modules 処理方法を既に知っているので、デフォルトの Babel 設定は ES Modules のトランスパイルを無効にします。ただし、Jest テストは Node で直接実行されるため、テスト用に有効にする必要があります。

また、babel-preset-env に、使用している Node のバージョンを指定するように指示することもできます。これにより不要な機能をスキップし、テストがより速く起動します。

これらのオプションをテストのためだけに適用するには、 env.test の下に別の設定をします(これは babel-jest によって自動的に選択されます)。

.babelrc:

{
  "presets": [
    ["env", { "modules": false }]
  ],
  "env": {
    "test": {
      "presets": [
        ["env", { "targets": { "node": "current" }}]
      ]
    }
  }
}

テストファイルの配置

デフォルトでは、Jest はプロジェクト全体で .spec.js または .test.js 拡張子を持つすべてのファイルを再帰的に取得します。これがあなたのニーズに合わない場合は、package.json ファイルの config セクションでtestRegex を変更することが可能です。

Jestは、テスト対象のコードのすぐ隣に__tests__ディレクトリを作成することを推奨していますが、適切にテストを構造化することは自由です。 Jestがスナップショットテストを実行するテストファイルの隣に__snapshots__ディレクトリを作成することに注意してください。

カバレッジ

Jest は複数のフォーマットでカバレッジを取ることができます。 以下はそれをするための簡単な例です。

jest の設定 (普通は package.json か jest.config.js) に collectCoverage オプションを加えます。それから、カバレッジを収集する対象のファイルを collectCoverageFrom に配列で定義します。

{
  "jest": {
    // ...
    "collectCoverage": true,
    "collectCoverageFrom": [
      "**/*.{js,vue}",
      "!**/node_modules/**"
    ]
  }
}

デフォルトのカバレッジレポーターのカバレッジレポートは有効になります。 coverageReporters オプションでそれらをカスタマイズすることができます。

{
  "jest": {
    // ...
    "coverageReporters": ["html", "text-summary"]
  }
}

より詳しい情報は Jest configuration documentation にあります。 カバレッジの閾値やターゲットを出力するディレクトリなどのオプションが記載されています。

Spec の例

あなたが Jasmine をよく知っているなら、Jest の assertion APIは自宅のように感じるはずです。

import { mount } from '@vue/test-utils'
import Component from './component'

describe('Component', () => {
  test('is a Vue instance', () => {
    const wrapper = mount(Component)
    expect(wrapper.isVueInstance()).toBeTruthy()
  })
})

スナップショットテスト

Vue Test Utils でコンポーネントをマウントすると、コンポーネントのルート HTML 要素にアクセスすることができます。 Jest のスナップショットテストで使用するためにこれを保存することができます。

test('renders correctly', () => {
  const wrapper = mount(Component)
  expect(wrapper.element).toMatchSnapshot()
})

カスタムシリアライザを使用することによって保存されたスナップショットを改善することができます。

npm install --save-dev jest-serializer-vue

package.json でその設定をします。

{
  // ...
  "jest": {
    // ...
    // スナップショットに対するシリアライズ
    "snapshotSerializers": [
      "jest-serializer-vue"
    ]
  }
}

リソース

Mocha + webpack による単一ファイルコンポーネントのテスト

このセットアップのサンプルプロジェクトは、 GitHub にあります。

単一ファイルコンポーネントをテストするためのもう一つの戦略は、webpack を使用してすべてのテストをコンパイルし、それをテストランナで実行することです。このアプローチの利点は、すべての webpack と vue-loader 機能を完全にサポートしていることです。ソースコードに妥協する必要はありません。

技術的に好きなテストランナを使用して結びつけることができますが、この特定の作業に非常に合理的な経験を提供するために mocha-webpack を見つけました。

mocha-webpack の設定

既に、webpack、vue-loader、および Babel が正しく設定されている設定から始めると仮定します。例: vue-cli によって雛形生成された webpack-simple テンプレートです。

最初に行うことは、テストの依存関係をインストールすることです:

npm install --save-dev @vue/test-utils mocha mocha-webpack

次に、package.json にスクリプトを定義する必要があります。

// package.json
{
  "scripts": {
    "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js"
  }
}

ここで注意すべき点がいくつかあります:

  • --webpack-config フラグはテストに使う webpack 設定ファイルを指定します。ほとんどの場合、これは実際のプロジェクトで使用する設定と同じですが、小さな調整が 1 つあります。これについては後で話します。

  • --require フラグは、テストの前に test/setup.js ファイルが実行されることを保証します。このテストでは、テストを実行するためのグローバル環境を設定できます。

  • 最後の引数は、テストバンドルに含まれるテストファイルのグロブです。

別の webpack の設定

NPM 依存関係の外部化

私たちのテストでは、いくつかの NPM 依存関係をインポートする可能性があります。これらのモジュールの中には、ブラウザの使用を念頭に置いて記述されているものもあれば、webpack によって適切にバンドルされていないものもあります。依存関係を外部化することにより、テストの起動速度が大幅に向上するというもう一つの考慮すべき点としてあります。webpack-node-externals を使って NPM の依存関係をすべて外部化することができます:

// webpack.config.js
const nodeExternals = require('webpack-node-externals')

module.exports = {
  // ...
  externals: [nodeExternals()]
}

ソースマップ

ソースマップは、mocha-webpack によってピックアップされるようにインライン化する必要があります。推奨設定は次のとおりです:

module.exports = {
  // ...
  devtool: 'inline-cheap-module-source-map'
}

IDE 経由でデバッグする場合は、以下を追加することをお勧めします:

module.exports = {
  // ...
  output: {
    // ...
    // ソースマップで絶対パスを使用する(IDE 経由のデバッグで重要)
    devtoolModuleFilenameTemplate: '[absolute-resource-path]',
    devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
  }
}

ブラウザ環境の設定

vue-test-utils はブラウザ環境を必要とします。jsdom-globalを 使って Node.js でシミュレーションすることができます:

npm install --save-dev jsdom jsdom-global

次に、test/setup.js の中で:

require('jsdom-global')()

これにより、node にブラウザ環境が追加され、 vue-test-utils が正しく動作するようになります。

検証ライブラリのピッキング

Chai は Mocha と並んで一般的に使用される一般的な検証ライブラリです。また、スパイとスタブを作成するための Sinon をチェックしてみてください。

あるいは、Jest の一部である expect を使うことができ、Jest のドキュメントにあるまったく同じAPIを公開しています。

ここで expect を使用してグローバルに利用できるようにして、すべてのテストでインポートする必要はありません。

npm install --save-dev expect

次に、test/setup.js の中で:

require('jsdom-global')()

global.expect = require('expect')

テストのための Babel の最適化

JavaScript を処理するには babel-loader を使用しています。.babelrc ファイルを使ってあなたのアプリでそれを使用しているならば、Babel を設定しておくべきです。babel-loader は自動的に同じ設定ファイルを使います。

注意すべき点の 1 つは、Node バージョン 6 以降を使用している場合、ES2015 の大部分の機能を既にサポートしているため、使用している Node のバージョンではサポートされていない機能のみをトランスパイルする Babel の env オプションを設定できます。(例えばstage-2や flow 構文のサポートなど)

テストを追加する

Counter.vue という名前の src ファイルを作成します。

<template>
	<div>
	  {{ count }}
	  <button @click="increment">Increment</button>
	</div>
</template>

<script>
export default {
  data () {
    return {
      count: 0
    }
  },

  methods: {
    increment () {
      this.count++
    }
  }
}
</script>

次のコードを使って test/Counter.spec.js という名前のテストファイルを作成します。

import { shallowMount } from '@vue/test-utils'
import Counter from '../src/Counter.vue'

describe('Counter.vue', () => {
  it('increments count when button is clicked', () => {
    const wrapper = shallowMount(Counter)
    wrapper.find('button').trigger('click')
    expect(wrapper.find('div').text()).toMatch('1')
  })
})

これでテストを実行できます:

npm run unit

やったー!テストを実行している!

カバレッジ

mocha-webpack にコードカバレッジをセットしたい場合は、 the mocha-webpack code coverage guide に従ってください。

リソース

Karma による単一ファイルコンポーネントのテスト

このセットアップの例は GitHub にあり、利用可能です。

Karma はブラウザを起動し、テストを実行しそれをレポートするテストランナーです。ここではテストを書くために Mocha を使用します。テストアサーションのために Chai を使用します。

Mocha をセットアップする

セットアップを始めるにあたって vue-cli でスキャフォールドされた webpack-simple テンプレートのように Webpack 、vue-loader 、 Babel が既に設定されていることを想定しています。

最初にテストに必要なライブラリをインストールします。

npm install --save-dev @vue/test-utils karma karma-chrome-launcher karma-mocha karma-sourcemap-loader karma-spec-reporter karma-webpack mocha

次に test スクリプトを package.json に定義します。

// package.json
{
  "scripts": {
    "test": "karma start --single-run"
  }
}
  • --single-run フラグは Karma にテストスウィートを1回実行するように指定します。

Karma の設定

プロジェクトディレクトリのトップに karma.conf.js ファイルを作成します。

// karma.conf.js

var webpackConfig = require('./webpack.config.js')

module.exports = function (config) {
  config.set({
    frameworks: ['mocha'],

    files: [
      'test/**/*.spec.js'
    ],

    preprocessors: {
      '**/*.spec.js': ['webpack', 'sourcemap']
    },

    webpack: webpackConfig,

    reporters: ['spec'],

    browsers: ['Chrome']
  })
}

このファイルは Karma を設定するために使用されます。

ファイルを Webpack で前処理する必要があります。そのために、 Webpack をプリプロセッサとして加えます。そして、 Webpack の設定を含めます。プロジェクトに元々あった Webpack の設定ファイルを変更なしで使用することができます。

この設定では、 Chrome でテストを実行します。他のブラウザを加える方法は Karma のドキュメントにあるブラウザセクション を見てください。

アサーションライブラリを選ぶ

Chai はよく Mocha と一緒に使用される人気のあるアサーションライブラリです。spy と stub を生成するために Sinon を見てみるとよいかもしれません。

テストで Chai を使うために karma-chai プラグインをインストールします。

npm install --save-dev karma-chai

テストを加える

Counter.vue ファイルを src ディレクトリに作成します。

<template>
  <div>
    {{ count }}
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      count: 0
    }
  },

  methods: {
    increment () {
      this.count++
    }
  }
}
</script>

そして、以下のようなコードの test/Counter.spec.js という名前のテストファイルを作成します。

import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import Counter from '../src/Counter.vue'

describe('Counter.vue', () => {
  it('increments count when button is clicked', () => {
    const wrapper = shallowMount(Counter)
    wrapper.find('button').trigger('click')
    expect(wrapper.find('div').text()).contains('1')
  })
})

テストを実行できるようになりました。

npm run test

やった!テストが走った。

カバレッジ

Karma にコードカバレッジをセットアップするために、 karma-coverage プラグインを使います。

デフォルトでは、 karma-coverage はソースマップをカバレッジレポートをマップすることに使用しません。だから、確実に正しくカバレッジがマップされるために babel-plugin-istanbul を使用します。

karma-coverage と babel-plugin-istanbul と cross-env をインストールします。

npm install --save-dev karma-coverage cross-env

環境変数の BABEL_ENV をセットするために cross-env を使います。テストをコンパイルする時に babel-plugin-istanbul を使用します。プロダクションコードをコンパイルする時は babel-plugin-istnabul を含めるべきではありません。

npm install --save-dev babel-plugin-istanbul

BABEL_ENV に test がセットされた時、 babel-plugin-istanbul を使用するために .babelrc ファイルを変更します。

{
  "presets": [
    ["env", { "modules": false }],
    "stage-3"
  ],
  "env": {
    "test": {
      "plugins": ["istanbul"]
    }
  }
}

カバレッジを取るために karma.conf.js ファイルを変更します。 coverage を reporters 配列に加えます。そして、 coverageReporter を加えます。

// karma.conf.js

module.exports = function (config) {
  config.set({
  // ...

    reporters: ['spec', 'coverage'],

    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    }
  })
}

そして、 BABEL_ENV をセットするために test スクリプトを変更します。

// package.json
{
  "scripts": {
    "test": "cross-env BABEL_ENV=test karma start --single-run"
  }
}

リソース

非同期動作のテスト

テストをシンプルにするために、 vue-test-utils はDOMの更新を同期的に適用します。しかし、コールバックや Promise のようなコンポーネントの非同期動作をテストする場合、いくつかのテクニックを知っておく必要があります。

よくある非同期動作の1つとして API 呼び出しと Vuex の action があります。以下の例は API 呼び出しをするメソッドをテストする方法を示しています。この例は HTTP のライブラリである axios をモックしてテストを実行するために Jest を使っています。Jestのモックの詳細はここにあります。

axios のモックの実装はこのようにします。

export default {
  get: () => new Promise(resolve => {
    resolve({ data: 'value' })
  })
}

ボタンをクリックすると以下のコンポーネントは API 呼び出しをします。そして、レスポンスを value に代入します。

<template>
  <button @click="fetchResults" />
</template>

<script>
  import axios from 'axios'

  export default {
    data () {
      return {
        value: null
      }
    },

    methods: {
      async fetchResults () {
        const response = await axios.get('mock/service')
        this.value = response.data
      }
    }
  }
</script>

テストはこのように書くことができます。

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'
jest.mock('axios')

test('Foo', () => {
  it('fetches async when a button is clicked', () => {
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click')
    expect(wrapper.vm.value).toBe('value')
  })
})

fetchResults 内の Promise が resolve する前にアサーションが呼ばれるので、このテストは現時点では失敗します。ほとんどのユニットテストライブラリはテストが完了したことをテストランナーに知らせるためのコールバック関数を提供します。Jest と Mocha は両方とも done を使います。アサーションが行われる前に確実に各 Promise が resolve するために done$nextTick や setTimeout と組み合わせて使うことができます。

test('Foo', () => {
  it('fetches async when a button is clicked', (done) => {
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click')
    wrapper.vm.$nextTick(() => {
      expect(wrapper.vm.value).toBe('value')
      done()
    })
  })
})

$nextTick と setTimeout がテストをパスする理由は $nextTick と setTimeout を処理するタスクキュー前に Promise のコールバック関数を処理するマイクロタスクキューが実行されるからです。つまり、$nextTick と setTimeout が実行される前に、マイクロタスクキュー上にあるすべての Promise のコールバック関数が実行されます。より詳しい説明はここを見てください。

もう1つの解決策は async function と npm パッケージの flush-promises を使用することです。flush-promises は堰き止められている resolve された Promise ハンドラを流します。堰き止められている Promise を流すこととテストの可読性を改善するために awaitflushPromises の呼び出しの前に置きます。

反映されたテストはこのようになります。

import { shallowMount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Foo from './Foo'
jest.mock('axios')

test('Foo', () => {
  it('fetches async when a button is clicked', async () => {
    const wrapper = shallowMount(Foo)
    wrapper.find('button').trigger('click')
    await flushPromises()
    expect(wrapper.vm.value).toBe('value')
  })
})

同じテクニックをデフォルトで Promise を返す Vuex の action に適用することができます。

TypeScript と一緒に使う

この記事のサンプルプロジェクトは、 GitHub にあります。

TypeScript は JavaScript に型とクラスを加えた人気のある JavaScript のスーパーセットです。 Vue Test Utils の型定義は、配布されているVue Test Utils のパッケージに含まれています。だから、Vue Test Utils と TypeScript はうまく動作します。

ここでは、基本的な Vue CLI を使った TypeScript のセットアップから Jest と Vue Test Utils を使用した TypeScript のテストの作成までを解説します。

TypeScript の追加

最初にプロジェクトを作成します。もし、Vue CLI をインストールしていないなら、 Vue CLI をグローバルにインストールしてください。

$ npm install -g @vue/cli

以下のようにプロジェクトを作成します。

$ vue create hello-world

CLI プロンプトで Manually select features を選択します。そして、 TypeScript を選択して Enter キーを押します。これで TypeScript の設定がされているプロジェクトが生成されます。

注意

Vue と TypeScript を一緒に使うためのセットアップの詳細は、 TypeScript Vue starter guide を確認してください。

次にプロジェクトに Jest を加えます。

Jest のセットアップ

Jest はバッテリー付属のユニットテストソリューションを提供するために Facebook が開発したテストランナです。 Jest の詳細については公式ドキュメント を参照してください。

Jest と Vue Test Utils をインストールします。

$ npm install --save-dev jest @vue/test-utils

次に test:unit スクリプトを package.json に定義します。

// package.json
{
  // ..
  "scripts": {
    // ..
    "test:unit": "jest"
  }
  // ..
}

Jest での単一ファイルコンポーネントの処理

Jest が *.vue ファイルを処理するために vue-jest プリプロセッサをインストールして設定します。

npm install --save-dev vue-jest

次に jest ブロックを package.json に追加します。

{
  // ...
  "jest": {
    "moduleFileExtensions": [
      "js",
      "ts",
      "json",
      // `*.vue` ファイルを Jest で取り扱います。
      "vue"
    ],
    "transform": {
      // `vue-jest` で `*.vue` ファイルを処理します。
      ".*\\.(vue)$": "vue-jest",
    },
    "testURL": "http://localhost/"
  }
}

Jest に対応するための TypeScript の設定

テストで TypeScript ファイルを使うために Jest が TypeScript をコンパイルするようにセットアップする必要があります。そのために ts-jest をインストールします。

$ npm install --save-dev ts-jest

次に Jest が TypeScript のテストファイルを ts-jest で処理するために package.jsonjest.transform に設定を追加します。

{
  // ...
  "jest": {
    // ...
    "transform": {
      // ...
      // `ts-jest` で `*.ts` ファイルを処理します。
      "^.+\\.tsx?$": "ts-jest"
    },
    // ...
  }
}

テストファイルの配置

デフォルトでは、 Jest はプロジェクトにある拡張子が .spec.js もしくは .test.js のすべてのファイルを対象にします。

拡張子が .ts のテストファイルを実行するために、package.json ファイルの testRegex を変更する必要があります。

以下を package.jsonjest フィールドに追加します。

{
  // ...
  "jest": {
    // ...
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$"
  }
}

Jest はテストされるコードと同じディレクトリに __tests__ ディレクトリを作成することを推奨していますが、あなたにとってテストに適したディレクトリ構造にして構いません。ただ、Jest は __snapshots__ ディレクトリをスナップショットテストを実施するテストファイルと同じディレクトリに作成することに注意してください。

ユニットテストを書く

これでプロジェクトのセットアップが完了しました。今度はユニットテストを作成します。

src/components/__tests__/HelloWorld.spec.ts ファイルを作成して、以下のコードを加えます。

// src/components/__tests__/HelloWorld.spec.ts
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'

describe('HelloWorld.vue', () => {
  test('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(HelloWorld, {
      propsData: { msg }
    })
    expect(wrapper.text()).toMatch(msg)
  })
})

これが TypeScript と Vue Test Utils を協業させるために必要なことすべてです!

リソース

Vue Router と一緒に使用する

テストへ Vue Router のインストール

テストで Vue のコンストラクタベースの Vue Router をインストールしないでください。Vue Router をインストールすると Vue のプロトタイプの読み取り専用プロパティとして $route$router が追加されます。

これを回避するために、localeVue を作成し、その上に Vue Router をインストールすることができます。

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'

const localVue = createLocalVue()
localVue.use(VueRouter)
const router = new VueRouter()

shallowMount(Component, {
  localVue,
  router
})

Vue Router を localVue にインストールすると $route と $router が読み取り専用プロパティーとして localVue に追加されます。これは VueRouter をインストールした localVue を使用しているコンポーネントをマウントする時、 mock オプションで $route と $router を上書きすることができないことを意味します。

Vue Router をインストールする時、router-linkrouter-view コンポーネントが登録されます。これは、それらをアプリケーションにインポートする必要がなく、アプリケーションのどこでも使用することができます。

テストを実行する際には、マウントしているコンポーネントにこれら Vue Router のコンポーネントを使用できるようにする必要があります。これらを行うには 2 つの方法があります。

スタブを使用する

import { shallowMount } from '@vue/test-utils'

shallowMount(Component, {
  stubs: ['router-link', 'router-view']
})

localVue による Vue Router のインストール

import { shallowMount, createLocalVue } from '@vue/test-utils'
import VueRouter from 'vue-router'

const localVue = createLocalVue()
localVue.use(VueRouter)

shallowMount(Component, {
  localVue
})

$route$router のモック

時々、コンポーネントが $route$router オブジェクトから引数によって何かをするテストをしたいときがあります。これをするためには、Vue インスタンスにカスタムモックを渡すことができます。

import { shallowMount } from '@vue/test-utils'

const $route = {
  path: '/some/path'
}

const wrapper = shallowMount(Component, {
  mocks: {
    $route
  }
})

wrapper.vm.$route.path // /some/path

よくある落とし穴

Vue Router をインストールすると Vue のプロトタイプに読み取り専用プロパティとして $route$router が追加されます。

これは、$route または $router をモックを試みるテストが将来失敗することを意味します。

これを回避するために、テストを実行するときに、Vue Router をグローバルにインストールしないでください。
上記のように localVue を使用してください。

Vuex と一緒に使用する

このガイドでは、vue-test-utils でコンポーネントで Vuex をテストする方法について、見ていきます。

コンポーネント内の Vuex をテストする

アクションのモック

それではいくつかのコードを見ていきましょう。

これはテストしたいコンポーネントです。これは Vuex のアクションを呼び出します。

<template>
    <div class="text-align-center">
      <input type="text" @input="actionInputIfTrue" />
      <button @click="actionClick()">Click</button>
    </div>
</template>

<script>
import { mapActions } from 'vuex'

export default{
  methods: {
    ...mapActions([
      'actionClick'
    ]),
    actionInputIfTrue: function actionInputIfTrue (event) {
      const inputValue = event.target.value
      if (inputValue === 'input') {
        this.$store.dispatch('actionInput', { inputValue })
      }
    }
  }
}
</script>

このテストの目的のために、アクションが何をしているのか、またはストアがどのように見えるかは気にしません。これらのアクションが必要なときに発行されていること、そして期待された値によって発行されていることを知ることが必要です。

これをテストするためには、私たちのコンポーネントを shallow するときに Vue にモックストアを渡す必要があります。

ストアを Vue コンストラクタベースに渡す代わりに、localVue に渡すことができます。localeVue はグローバルな Vue コンストラクタに影響を与えずに、変更を加えることができるスコープ付き Vue コンストラクタです。

これがどのように見えるか見ていきましょう:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Actions from '../../../src/components/Actions'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('Actions.vue', () => {
  let actions
  let store

  beforeEach(() => {
    actions = {
      actionClick: jest.fn(),
      actionInput: jest.fn()
    }
    store = new Vuex.Store({
      state: {},
      actions
    })
  })

  it('dispatches "actionInput" when input event value is "input"', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    const input = wrapper.find('input')
    input.element.value = 'input'
    input.trigger('input')
    expect(actions.actionInput).toHaveBeenCalled()
  })

  it('does not dispatch "actionInput" when event value is not "input"', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    const input = wrapper.find('input')
    input.element.value = 'not input'
    input.trigger('input')
    expect(actions.actionInput).not.toHaveBeenCalled()
  })

  it('calls store action actionClick when button is clicked', () => {
    const wrapper = shallowMount(Actions, { store, localVue })
    wrapper.find('button').trigger('click')
    expect(actions.actionClick).toHaveBeenCalled()
  })
})

ここでは何が起こっているでしょうか?まず、Vue に localVue.use メソッドを使用して Vuex を使用するように指示しています。これは、単なる Vue.use のラッパです。

次に、新しい Vuex.store をモックした値で呼び出すことによってモックのストアを作成します。それをアクションに渡すだけです。それが気にしなければならないことの全てだからです。

アクションは、Jest のモック関数です。これらモック関数は、アクションが呼び出された、または呼び出されていない、かどうかを検証するメソッドを提供します。

アクションのスタブが期待どおりに呼び出されたことを検討することができます。

今、ストアを定義する方法が、あなたには少し異質に見えるかもしれません。

各テストより前にストアをクリーンに保証するために、beforeEach を使用しています。beforeEach は各テストより前に呼び出される Mocha のフックです。このテストでは、ストア変数に値を再度割り当てています。これをしていない場合は、モック関数は自動的にリセットされる必要があります。また、テストにおいて状態を変更することもできますが、この方法は、後のテストで影響を与えることはないです。

このテストで最も重要なことは、モック Vuex ストアを作成し、それを vue-test-utils に渡す ことです。

素晴らしい!今、アクションをモック化できるので、ゲッタのモックについて見ていきましょう。

ゲッタのモック

<template>
    <div>
      <p v-if="inputValue">{{inputValue}}</p>
      <p v-if="clicks">{{clicks}}</p>
    </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default{
  computed: mapGetters([
    'clicks',
    'inputValue'
  ])
}
</script>

これは、かなり単純なコンポーネントです。ゲッタによる clicks の結果と inputValue を描画します。また、これらゲッタが返す値については実際に気にしません。それらの結果が正しく描画されているかだけです。

テストを見てみましょう:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import Getters from '../../../src/components/Getters'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('Getters.vue', () => {
  let getters
  let store

  beforeEach(() => {
    getters = {
      clicks: () => 2,
      inputValue: () => 'input'
    }

    store = new Vuex.Store({
      getters
    })
  })

  it('Renders state.inputValue in first p tag', () => {
    const wrapper = shallowMount(Getters, { store, localVue })
    const p = wrapper.find('p')
    expect(p.text()).toBe(getters.inputValue())
  })

  it('Renders state.clicks in second p tag', () => {
    const wrapper = shallowMount(Getters, { store, localVue })
    const p = wrapper.findAll('p').at(1)
    expect(p.text()).toBe(getters.clicks().toString())
  })
})

このテストはアクションのテストに似ています。各テストの前にモックストアを作成し、shallowMount を呼び出すときにオプションを渡し、そしてモックゲッタから返された値を描画されているのを検証します。

これは素晴らしいですが、もしゲッタが状態の正しい部分を返しているのを確認したい場合はどうしますか?

モジュールによるモック

モジュールはストアを管理しやすい塊に分けるために便利です。それらはゲッタもエクスポートします。テストではこれらを使用することができます。

コンポーネントを見てみましょう:

<template>
  <div>
    <button @click="moduleActionClick()">Click</button>
    <p>{{moduleClicks}}</p>
  </div>
</template>

<script>
import { mapActions, mapGetters } from 'vuex'

export default{
  methods: {
    ...mapActions([
      'moduleActionClick'
    ])
  },

  computed: mapGetters([
    'moduleClicks'
  ])
}
</script>

1 つのアクションと 1 つのゲッタを含む単純なコンポーネントです。

そしてテストは以下のようになります:

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import MyComponent from '../../../src/components/MyComponent'
import myModule from '../../../src/store/myModule'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('MyComponent.vue', () => {
  let actions
  let state
  let store

  beforeEach(() => {
    state = {
      clicks: 2
    }

    actions = {
      moduleActionClick: jest.fn()
    }

    store = new Vuex.Store({
      modules: {
        myModule: {
          state,
          actions,
          getters: myModule.getters
        }
      }
    })
  })

  it('calls store action "moduleActionClick" when button is clicked', () => {
    const wrapper = shallowMount(MyComponent, { store, localVue })
    const button = wrapper.find('button')
    button.trigger('click')
    expect(actions.moduleActionClick).toHaveBeenCalled()
  })

  it('renders "state.inputValue" in first p tag', () => {
    const wrapper = shallowMount(MyComponent, { store, localVue })
    const p = wrapper.find('p')
    expect(p.text()).toBe(state.clicks.toString())
  })
})

Vuex ストアのテスト

Vuex ストアをテストする方法が2つあります。1つ目はゲッタとミューテーションとアクションを別々に単体テストする方法です。2つ目はストアを生成してそれをテストする方法です。

Vuex ストアをテストする方法を説明するためにシンプルなカウンターストアを用意します。このストアには increment ミューテーションと evenOrOdd ゲッタがあります。

// mutations.js
export default {
  increment (state) {
    state.count++
  }
}
// getters.js
export default {
  evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'
}

ゲッタとミューテーションとアクションを別々にテストする

ゲッタとミューテーションとアクションはすべて JavaScript の関数です。それらは vue-test-utils と Vuex を使用しなくてもテストすることができます。

ゲッタとミューテーションとアクションを別々にテストする利点は単体テストを詳細に記述することができることです。テストが失敗すると、コードの何が原因か正確に知ることができます。欠点は commit や dispatch のような Vuex の関数のモックが必要なことです。これは不正なモックが原因で単体テストはパスしてプロダクションは失敗する状況を作り出す可能性があります。

mutations.spec.js と getters.spec.js という名前のテストファイルを2つ作成します。

最初に increment ミューテーションをテストします。

// mutations.spec.js

import mutations from './mutations'

test('increment increments state.count by 1', () => {
  const state = {
    count: 0
  }
  mutations.increment(state)
  expect(state.count).toBe(1)
})

今度は evenOrOdd ゲッタを次の手順でテストします。 state モックを作成します。 state を引数としてゲッタ関数を実行します。そして、それが正しい値を返したか確認します。

// getters.spec.js

import getters from './getters'

test('evenOrOdd returns even if state.count is even', () => {
  const state = {
    count: 2
  }
  expect(getters.evenOrOdd(state)).toBe('even')
})

test('evenOrOdd returns odd if state.count is odd', () => {
  const state = {
    count: 1
  }
  expect(getters.evenOrOdd(state)).toBe('odd')
})

実行可能なストアのテスト

Vuexストアをテストするもう1つの方法はストアの設定を使って実行可能なストアを生成することです。

実行可能なストアを生成してテストすることの利点は Vuex の関数をモックする必要がない事です。

欠点はテストが失敗した時、問題がある箇所を見つけることが難しいことです。

テストを書いてみましょう。ストアを生成する際は、 Vue のコンストラクタが汚染されることを避けるために localVue を使用します。このテストは store-config.js の export を使用してストアを生成します。

// store-config.js

import mutations from './mutations'
import getters from './getters'

export default {
  state: {
    count: 0
  },
  mutations,
  getters
}
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import storeConfig from './store-config'
import { cloneDeep } from 'lodash'

test('increments count value when increment is commited', () => {
  const localVue = createLocalVue()
  localVue.use(Vuex)
  const store = new Vuex.Store(cloneDeep(storeConfig))
  expect(store.state.count).toBe(0)
  store.commit('increment')
  expect(store.state.count).toBe(1)
})

test('updates evenOrOdd getter when increment is commited', () => {
  const localVue = createLocalVue()
  localVue.use(Vuex)
  const store = new Vuex.Store(cloneDeep(storeConfig))
  expect(store.getters.evenOrOdd).toBe('even')
  store.commit('increment')
  expect(store.getters.evenOrOdd).toBe('odd')
})

ストアをストアの設定から生成する前に cloneDeep を使用しています。こうする理由は Vuex はストアを生成するためにオプションオブジェクトを変更するからです。どのテストでも確実に汚染されていないストアを使うために storeConfig オブジェクトを複製する必要があります。

リソース