N-LAB

Nuxt 3 × VitestでVeeValidate 4の単体テストを実装する

更新日: 2024年12月29日


目標


前提


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

・Nuxt 3.15.0
・Vitest 2.1.8
・VeeValidate 4.15.0
・@vee-validate/i18n 4.15.0
・@vee-validate/rules 4.15.0
・@testing-library/user-event 14.5.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 を新規作成して以下の内容で保存します。

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

export default defineNuxtPlugin((_nuxtApp) => {
  configure({
    generateMessage: localize({
      // エラーメッセージの日本語化
      ja,
    }),
  })

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

  // エラーメッセージの日本語化
  setLocale('ja')
})


3. pagesディレクトリに form.vue を新規作成して以下の内容で保存します。

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

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

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


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

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

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

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

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



1. テストファイルや設定ファイルを保存するためのディレクトリをルート配下に作成します。

mkdir unitTests

※ソースディレクトリを変更している場合は、変更先のディレクトリ内に作成してください。

2. setup.tsunitTests配下に新規作成して以下の内容で保存します。

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

// vee-validate setup
configure({
  generateMessage: localize({
    // エラーメッセージの日本語化
    ja,
  }),
})

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

// エラーメッセージの日本語化
setLocale('ja')


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

// vitest.config.ts
export default defineConfig({
  test: {
    // テスト実行時に以下で指定したファイルが読み込まれます。
    setupFiles: './unitTests/setup.ts'
  }
})


単体テストコードの実装

1. unitTests配下に form.spec.ts を新規作成して以下の内容で保存します。

// unitTests/form.spec.ts
import { describe, expect, test, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import Form from '~/pages/form.vue'

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

      await waitFor(() => {
        // Assert
        const isDisabled = (screen.getByRole('button') as HTMLButtonElement).disabled
        expect(isDisabled).toBe(true)
      })
    })
  })

  describe('vee-validateの動作確認', () => {
    test('emailは入力必須の項目であること', async () => {
      // Arrange
      const user = userEvent.setup()
      render(Form)
      const inputElement = screen.getByPlaceholderText('email')

      // Act
      await user.type(inputElement, '{Tab}')

      // Assert
      expect(screen.getByText('emailは必須項目です')).toBeTruthy()
    })

    test('emailの入力は有効なメールアドレス形式であること', async () => {
      // Arrange
      const user = userEvent.setup()
      render(Form)
      const email = screen.getByPlaceholderText('email')

      // Act
      await user.type(email, 'abc{Tab}')

      // Assert
      expect(screen.getByText('emailは有効なメールアドレスではありません')).toBeTruthy()
    })

    test('有効なemailを入力した場合は、送信ボタンが活性になること', async () => {
      // Arrange
      const user = userEvent.setup()
      render(Form)

      // Act
      await user.type(screen.getByPlaceholderText('email'), 'abc@abc.com')

      await waitFor(() => {
        // Assert
        const isDisabled = (screen.getByRole('button') as HTMLButtonElement).disabled
        expect(isDisabled).toBe(false)
      })
    })

    test('送信ボタンを押下した場合は、送信処理が実行されること', async () => {
      // Arrange
      const user = userEvent.setup()
      const handleSubmitMock = vi.fn()
      render(Form, {
        global: {
          mocks: {
            handleSubmit: handleSubmitMock
          },
        }
      })
      await user.type(screen.getByPlaceholderText('email'), 'abc@abc.com')

      // Act
      await user.click(screen.getByRole('button'))

      await waitFor(() => {
        // Assert
        expect(handleSubmitMock).toHaveBeenCalledOnce()
      })
    })
  })
})


2. テストを実行します。全件passすることを確認します。

$ npm run test
> test
> vitest
 ✓ unitTests/form.spec.ts
   ✓ Form (5) 529ms
     ✓ 画面初期状態の確認 (1)
       ✓ 送信ボタンが非活性であること
     ✓ vee-validateの動作確認 (4) 495ms
       ✓ emailは入力必須の項目であること
       ✓ emailの入力は有効なメールアドレス形式であること
       ✓ 有効なemailを入力した場合は、送信ボタンが活性になること
       ✓ 送信ボタンを押下した場合は、送信処理が実行されること
 Test Files  1 passed (1)
      Tests  5 passed (5)

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

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



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