VeeValidateを利用したコンポーネントをJestでテストする

# はじめに
Vue v3及び、CompositionAPIで作られたコンポーネントでvee-validate v3系を対象に、Jestでテストを書きます

以下のようなコンポーネントを想定

- validationProviderでinputをvalidate
- validationObserverで各Providerを監視

```vue:child-input.vue
<template>
  <validation-provider mode="eager" rules="required">
    <input
      v-model="val"
      name="input"
    >
    <ul v-if="errors.length" class="errors">
      <li v-for="(error, i) in errors" :key="i">
        {{ error }}
      </li>
    </ul>
  </validation-provider>
</template>

<script lang="ts">
export default {
  setup(_, { emit }) {
    const val = ref<string>('')
    watch(() => {
      emit('update', val.value)
    })
    return { val }
  }
}
</script>
```

```vue:parent-form.vue
<template>
  <validation-observer ref="observer">
    <form>
      <child-input @update="update" />
    </form>
  </validation-observer>
</template>

<script lang="ts">
export default {
  setup(_, { emit }) {
    const val = ref<string>('')
    const update = (value: string) => {
      val.value = value
    }
    return {
      val,
      update
    }
  }
}
</script>
```

# テスト
それではテストを書いていきます

```ts:form-validation.spec.ts
import { createLocalVue, mount } from '@vue/test-utils'
import { ValidationObserver, ValidationProvider, extend } from 'vee-validate'
import ParentForm from '@your/path/parent-form.vue'

const localVue = createLocalVue()
localVue.component('ValidationObserver', ValidationObserver)
localVue.component('ValidationProvider', ValidationProvider)

const { required } = require('vee-validate/dist/rules.umd')
extend('required', required)

jest.useFakeTimers()

describe('フォームのバリデーションチェック', () => {
  test('未入力でバリデーションされること', async (): Promise<any> => {
    const wrapper = mount(ParentForm, { localVue })
    const observer: InstanceType<typeof ValidationObserver> = wrapper.vm.$refs.observer
    await observer.validate()
    jest.runAllTimers()
    await wrapper.vm.$nextTick()
    expect(wrapper.find('.errors').exists()).toBeTruthy()
  })
  test('入力が完了すること', async (): Promise<any> => {
    const wrapper = mount(ParentForm, { localVue })
    wrapper.find('input').setValue('hoge')
    const observer: InstanceType<typeof ValidationObserver> = wrapper.vm.$refs.observer
    await observer.validate()
    jest.runAllTimers()
    await wrapper.vm.$nextTick()
    expect(wrapper.find('.errors').exists()).toBeFalsy()
  })
})
```

# テストの解説

それぞれの処理について見ていきます。

```ts
const wrapper = mount(ParentForm, { localVue })
```
今回は子コンポーネントもテスト対象のため`shalowMount`ではなく`mount`を利用しています。古い記事を参照すると`mount`のオプションに`{ sync: false }`を追加するという情報がありますがそのオプションは廃止されています。
https://github.com/vuejs/vue-test-utils/issues/1137

```ts
const observer: InstanceType<typeof ValidationObserver> = wrapper.vm.$refs.observer
await observer.validate()
```
ValidationObserverインスタンスを取得して`validate`メソッドを実行します。これによってvee-validateのバリデーションが実行されます。

```ts
jest.runAllTimers()
```
ValidationObserverの処理に16msかかるそうで、それを待ちます。
https://logaretm.github.io/vee-validate/advanced/testing.html#testing-validationobserver-debounced-state

```ts
await wrapper.vm.$nextTick()
```
先程`{ sync: false }`オプションがなくなったことで画面描画を待つために、`$nextTick`を利用します。このメソッドがPromiseを返すためawaitで待つことによってこの後の処理ではDOMに反映された状態を取得することができます。
vee-validate公式では`flush-promises`を使う例を紹介されていますが$nextTickを利用することで同様の結果を得られるので不要になるかと思います。

# まとめ
- `sync`オプションはもう使えない
- `flush-promises`ではなく`$nextTick`を使おう