N-LAB

Nuxt 3 × VitestでVeeValidate 4を使用するVueファイルの単体テストを実行する


目標


前提

※本手順書ではソースディレクトリを「src」に変更している想定としています。テストコードは「src/unitTest」に保存します。

https://n-laboratory.jp/articles/nuxt3-vitest-unittest

・Nuxt 3.4.3
・Vitest 0.31.0
・VeeValidate 4.9.3
・@vee-validate/i18n 4.9.3
・@vee-validate/rules 4.9.3
・flush-promises 1.0.2

目次

  1. VeeValidateインストール
  2. VitestでVeeValidateを利用するための初期設定
  3. 単体テストコードの実装


VeeValidateインストール

1. VeeValidateをインストールします。

$ npm install --save-dev vee-validate @vee-validate/i18n @vee-validate/rules


2. 「plugins」ディレクトリに「vee-validate-plugin.ts」を新規作成し、以下を追加します。

import { localize, setLocale } from '@vee-validate/i18n'
import ja from '@vee-validate/i18n/dist/locale/ja.json'
import AllRules from '@vee-validate/rules'
import { defineRule, configure } from 'vee-validate'
import { defineNuxtPlugin } from '#app'

export default defineNuxtPlugin((_nuxtApp) => {
  configure({
    generateMessage: localize({
      ja
    })
  })

  Object.keys(AllRules).forEach((rule) => {
    // すべてのルールをインポート
    defineRule(rule, AllRules[rule])
  })

  setLocale('ja')
})


3. 「pages」ディレクトリに「form.vue」を新規作成し、以下を追加します。

<script lang="ts" setup>
import { Form, Field, ErrorMessage } from 'vee-validate'

// 送信ボタン押下時に実行される関数
const foo = (values: Record<string, any>) => {
  console.log(values.email)
}
</script>

<template>
  <Form v-slot="{ meta }" data-testid="validation-form" @submit="foo">
    <!-- バリデーション対象の項目 -->
    <Field rules="required|email" name="email" as="input" type="text" data-testid="input-email" />
    <!-- バリデーションエラーメッセージの表示 -->
    <ErrorMessage name="email" data-testid="email-error-msg" />
    <!-- meta.validは全項目で有効な値を入力された場合にtrueを返し、無効な値を入力または初期状態はfalseを返す -->
    <button :disabled="!meta.valid" data-testid="submit-btn">Submit</button>
  </Form>
</template>


4. 以下のURLにアクセスします。テキストボックスと送信ボタンが表示されていることを確認します。
http://localhost:3000/form

5. テキストボックスに「test」を入力するとエラーメッセージが表示されることを確認します。

6. テキストボックスが未入力の場合にエラーメッセージが表示されることを確認します。

7. テキストボックスに「test@test.com」を入力するとエラーメッセージが表示されず、送信ボタンが活性することを確認します。

VitestでVeeValidateを利用するための初期設定



1. 「setup.ts」を新規作成し、以下の内容を追加します。

import { localize, setLocale } from '@vee-validate/i18n'
import ja from '@vee-validate/i18n/dist/locale/ja.json'
import AllRules from '@vee-validate/rules'
import { defineRule, configure } from 'vee-validate'

// vee-validateセットアップ
configure({
  generateMessage: localize({
    ja
  })
})

Object.keys(AllRules).forEach((rule) => {
  // すべてのバリデーションルール読み込み
  defineRule(rule, AllRules[rule])
})

setLocale('ja')


2. 「vitest.config.tsの「setupFiles」に「setup.ts」のパスを追加します

export default defineConfig({
  test: {
    setupFiles: './src/unitTest/setup.ts'
  }
})

※本手順書ではテスト用のディレクトリとして「unitTest」を作成しています。

単体テストコードの実装

https://vee-validate.logaretm.com/v4/guide/testing#waiting-for-async-validation

1. 「flush-promise」をインストールします。

$ npm install --save-dev flush-promises


2. 「form.spec.ts」を新規作成し、以下の内容を追加します。

import { describe, expect, test } from 'vitest'
import { fireEvent, render } from '@testing-library/vue'
import flushPromises from 'flush-promises'
import Form from '~/pages/form.vue'

describe('Form', () => {
  describe('画面初期状態の確認', () => {
    test('送信ボタンが非活性であること', async () => {
      // Arrange
      const { container } = render(Form)

      // render直後はHTMLButtonElement.disabledが常にfalseを返すため、flushPromisesを使用する必要があります。
      await flushPromises()
      const disabled = (container.querySelector('[data-testid="submit-btn"]') as HTMLButtonElement).disabled

      // Assert
      expect(disabled).toBeTruthy()
    })
  })

  describe('VeeValidateの動作確認', () => {
    test.each([
      ['email形式', 'abc', 'emailは有効なメールアドレスではありません'],
      ['必須', '', 'emailは必須項目です']
    ])(
      'Email入力欄への入力は%sであること',
      async (
        type, value, expectedErrorMsg
      ) => {
        // Arrange
        const { container } = render(Form)
        const inputElement = container.querySelector('[data-testid="input-email"]') as HTMLInputElement

        // Act
        await fireEvent.update(inputElement, value)
        await fireEvent.blur(inputElement)
        // フラシュしないと期待した結果が得られないので注意
        await flushPromises()
        const actualErrorMsg = container.querySelector('[data-testid="email-error-msg"]')?.textContent

        // Assert
        expect(actualErrorMsg).toBe(expectedErrorMsg)
      })

    test('有効なEmailを入力した場合は送信ボタンが活性になること', async () => {
      // Arrange
      const { container } = render(Form)
      const inputElement = container.querySelector('[data-testid="input-email"]') as HTMLInputElement

      // Act
      await fireEvent.update(inputElement, 'abc@abc.com')
      await fireEvent.blur(inputElement)
      await flushPromises()
      const disabled = (container.querySelector('[data-testid="submit-btn"]') as HTMLButtonElement).disabled

      // Assert
      expect(disabled).toBeFalsy()
    })

    test('送信ボタンを押下した場合は送信処理が実行されること', async () => {
      const submitFn = vi.fn()

      // Arrange
      const { container } = render(Form, { global: { mocks: { foo: submitFn } } })
      const inputElement = container.querySelector('[data-testid="input-email"]') as HTMLInputElement

      // Act
      await fireEvent.update(inputElement, 'abc@abc.com')
      await fireEvent.blur(inputElement)
      await flushPromises()

      await fireEvent.click((container.querySelector('[data-testid="submit-btn"]') as HTMLButtonElement))
      await flushPromises()

      // Assert
      expect(submitFn).toHaveBeenCalledOnce()
    })
  })
})


3. 「form.spec.ts」のテストを実行します。全件passすることを確認します。

$ npm run test
> test
> vitest
 ✓ src/unitTest/form.spec.ts (5)
   ✓ Form (5)
     ✓ 画面初期状態の確認 (1)
       ✓ 送信ボタンが非活性であること
     ✓ VeeValidateの動作確認 (4)
       ✓ Email入力欄への入力はemail形式であること
       ✓ Email入力欄への入力は必須であること
       ✓ 有効なEmailを入力した場合は送信ボタンが活性になること
       ✓ 送信ボタンを押下した場合は送信処理が実行されること
 Test Files  1 passed (1)
      Tests  5 passed (5)

※本手順書では「package.json」の「scripts」に以下が追加されている想定としています。

{
  "scripts": {
    "test": "vitest",
  },
}



以上で全ての手順は完了になります