LoginSignup
13
7

More than 3 years have passed since last update.

VeeValidateを使用したコンポーネントのユニットテストのエラーを解消する

Last updated at Posted at 2019-01-08

※本記事は VeeValidate v2向けです。v3でのユニットテストのやり方については 公式ドキュメント を参照してください。


VeeValidateを使ったコンポーネントの書き方には、注意が必要なポイントがあります。特に、vue-test-utilsと組み合わせて使っている場合、謎のエラーに悩まされる可能性があります。

先に結論

  • mount/shallowMountsync: false にすること
  • flush-promises 等で、バリデーションの完了を待つこと

VeeValidate + vue-test-utilsのありがちなエラー

以下のようなコンポーネントがあります。nameフィールドが未入力(null/undefined/空文字列)だと、エラーメッセージが表示されます。

<template>
  <form>
    <label>
      Name: <input name="name" v-model="name" v-validate="'required'" />
    </label>
    <p style="color:red">{{ errors.first('name') }}</p>
  </form>
</template>

<script>
export default {
  name: 'EntryForm',
  data() {
    return {
      name: '',
    }
  }
}
</script>

このフォームのバリデーションをテストしてみます。mocha + chai + vue-test-utilsでテストを書くと、こんな感じになると思います。テスト用のVueコンストラクタをcreateLocalVueで作成し、これにVeeValidateをインストールしているところがミソです。

import { expect } from 'chai'
import { shallowMount, createLocalVue } from '@vue/test-utils'
import VeeValidate from 'vee-validate'
import EntryForm from '@/components/EntryForm.vue'

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

describe('EntryForm', () => {
  it('The name field is required', async () => {
    const wrapper = shallowMount(EntryForm, {
      localVue,
    })
    wrapper.find('[name="name"]').setValue('')
    wrapper.vm.$validator.validateAll()
    expect(wrapper.vm.errors.first('name')).to.equal('The name field is required.')
  })
})

このテストを実行してみると、Macだとエラーが大量に表示され、WindowsだとStack Overflow(exit code 3221225725)が発生します。

エラーの原因は、vue-test-utilsがデフォルトでDOMの更新を同期的に適用するのに対して、VeeValidateは非同期なDOMの更新(Vueのデフォルトの動作)を期待しているからです。この辺の事情はvue-test-utilsドキュメントの↓の記事に詳しいです。
https://vue-test-utils.vuejs.org/ja/guides/testing-async-components.html

テストが動くようにする

上記記事にも言及があるように、vue-test-utilsでDOMの更新を非同期的に適用するようにするには、shallowMountの第2引数に { sync:false } を渡します。

    const wrapper = shallowMount(EntryForm, {
      localVue,
+     sync: false,
    })

この状態で再度実行すると、先ほどのエラーは消えますが、テストは依然として失敗します。バリデーションエラーになるはずなのに、エラーにならない、という状態になります。これは、バリデーションの反映が非同期に行われるためです。そこで、flush-promisesライブラリ等を使用して、全ての非同期処理が完了するのを待ちます。

 import { expect } from 'chai'
 import { shallowMount, createLocalVue } from '@vue/test-utils'
 import VeeValidate from 'vee-validate'
+import flushPromises from 'flush-promises'
 import EntryForm from '@/components/EntryForm.vue'

 const localVue = createLocalVue()
@@ -15,7 +14,6 @@ describe('EntryForm', () => {
     })
     wrapper.find('[name="name"]').setValue('')
     wrapper.vm.$validator.validateAll()
+    await flushPromises()
     expect(wrapper.vm.errors.first('name')).to.equal('The name field is required.')
   })
 })

これで、テストが正常に実行されるようになります。

  EntryForm
    ✓ The name field is required (42ms)


  1 passing (45ms)

 MOCHA  Tests completed successfully

まとめ

冒頭の結論を再掲します:

  • mount/shallowMountsync: false にすること
  • flush-promises 等で、バリデーションの完了を待つこと

flush-promisesはvue-test-utilsで非同期処理のテストを書いていれば頻出のライブラリですが、VeeValidateがsync:falseでないと動かないのは落とし穴ですね。

13
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7