
1. はじめに
こんにちは!クロスマートのフロントエンドエンジニアの朝井です。
今年5月にクロスマートに入社してから早くも3ヶ月が経ちました。新しい環境での挑戦の中で特に注目しているのがフロントエンドのテスト環境です。テストはこれまで得意ではありませんでしたが、日々新しいことを学びながら取り組んでいます。
私自身、テストを書くときに「本当に必要なのか?」「何をテストすればいいの?」といった疑問を抱えていました。また、技術の進化が早いフロントエンドの世界では、せっかく書いたテストがすぐに無駄になってしまうのではないかという不安もありました。
本記事では、それを乗り越えるためのヒントを共有していきます。フロントエンドのテストに挑戦している皆さんに少しでも役立つ情報をお届けできればと思います。
2. 何をテストすれば良いのか
コンポーネントはインプットとアウトプットの関数
フロントエンドのコンポーネントは、一見複雑に見えるかもしれませんが、基本的にはインプット(入力)とアウトプット(出力)の単純な関数と考えることができます。具体的には、コンポーネントに対してプロパティやユーザー操作をインプットとして与えると、それに応じたDOMがアウトプットとして生成されます。
例えば、以下のようなシンプルなコンポーネントを考えてみましょう。
<template>
<button :class="{ active: isActive }" @click="handleClick">{{ label }}</button>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
type Props = {
label: string
isActive?: boolean
}
defineProps<Props>()
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click')
}
</script>
このコンポーネントでは、label と isActive というプロパティがインプットとなり、その結果として生成されるボタンのDOMがアウトプットです。この関係を理解することで、何をテストすべきかが明確になります。
コンポーネントにおけるアウトプットはDOM
コンポーネントのテストでは、インプットに対して正しいアウトプット(DOM)が生成されるかどうかを確認します。例えば、以下のようなテストケースが考えられます。
// MyButton.test.ts
import { mount } from '@vue/test-utils'
import MyButton from './MyButton.vue'
import { describe, it, expect } from 'vitest'
describe('MyButton', () => {
it('ラベルがレンダリングされる', () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me' }
})
expect(wrapper.text()).toBe('Click Me')
})
it('isActive が true のときに active クラスが適用される', () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me', isActive: true }
})
expect(wrapper.classes()).toContain('active')
})
})
イベントのテスト方法
イベントのテストも重要な部分です。ユーザーがボタンをクリックしたときに適切なイベントが発生するかを確認する必要があります。例えば、先ほどの MyButton コンポーネントのクリックイベントをテストするには、以下のようにします。
// MyButton.test.ts
import { mount } from '@vue/test-utils'
import MyButton from './MyButton.vue'
import { describe, it, expect } from 'vitest'
describe('MyButton', () => {
it('ラベルがレンダリングされる', () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me' }
})
expect(wrapper.text()).toBe('Click Me')
})
it('isActive が true のときに active クラスが適用される', () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me', isActive: true }
})
expect(wrapper.classes()).toContain('active')
})
it('クリックイベントが発火する', async () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me' }
})
await wrapper.trigger('click')
expect(wrapper.emitted()).toHaveProperty('click')
})
})
このように、コンポーネントに対して入力を与え、その結果として生成されるDOMや発生するイベントを確認することで、テストを行います。
3. まずは小さく始めること
テストしやすいコンポーネントとそうでないコンポーネント
すべてのコンポーネントが同じようにテストしやすいわけではありません。テストしやすいコンポーネントとそうでないコンポーネントの違いを理解することは重要です。
- テストしやすいコンポーネント: 単純なインプットとアウトプットを持ち、依存関係が少ないコンポーネント。
- テストしにくいコンポーネント: 多くの依存関係を持ち、複雑なロジックや状態管理を含むコンポーネント。
テストしやすいコンポーネントから始めることで、テストの基本を理解し、テストを書く習慣をつけることができます。
コンポーネントを小さくすることのメリット
そもそもコンポーネントを小さくすることは、テストがしやすくなるだけでなく、以下のような多くのメリットがあります。
- 再利用性の向上: 小さなコンポーネントは再利用しやすく、コードの重複を避けることができます。
- 保守性の向上: 単機能の小さなコンポーネントは、変更や修正が容易です。
- 可読性の向上: 小さなコンポーネントは、コードの読みやすさを向上させます。
まずは、小さなコンポーネントから始め、それを続けることで、テストのメリットを実感できるようになるでしょう。次は、具体的な環境設定から始めましょう。
4. 環境設定
Vue3とVitest
Vue.jsは、直感的で柔軟なJavaScriptフレームワークで、シンプルなAPIを提供し、複雑なUIを簡単に構築することができます。Vue3は、その最新バージョンで、パフォーマンスの向上や新機能の追加が行われており、より快適な開発体験を提供します。
Vitestは、Viteとシームレスに統合できるテストフレームワークで、Vueコンポーネントのテストに適しています。軽量で高速なテスト実行が可能であり、Jestに似た使い勝手を持ちながらも、Viteの開発環境と完全に統合されています。
必要なツールのインストール
まず、Node.jsとnpmがインストールされていることを確認しましょう。これらは、JavaScriptのパッケージ管理と環境構築に必要です。
create-vueを使ったVueプロジェクトの作成
- create-vueは、ViteベースのVueプロジェクトの初期設定を自動化するツールです。
- create-vueでは、Vitestを動かすためのオプションが選択できるため、最も簡単にVueのコンポーネントテストを体験できます。
- プロジェクトを作成するには、以下のコマンドを実行します。
npm create vue@latest

- インタラクティブな質問に答えてプロジェクトの設定を行います。
Vitestの設定を確認
vite.config.tsファイルにVitestの設定が含まれていることを確認します。import { fileURLToPath } from 'node:url' import { mergeConfig, defineConfig, configDefaults } from 'vitest/config' import viteConfig from './vite.config' export default mergeConfig( viteConfig, defineConfig({ test: { environment: 'jsdom', exclude: [...configDefaults.exclude, 'e2e/**'], root: fileURLToPath(new URL('./', import.meta.url)) } }) )
5. サンプルテストの作成
コンポーネントディレクトリに先ほどのButtonコンポーネントを作成してテストを記述してみましょう。
コンポーネントの作成
まず、src/componentsディレクトリに移動し、MyButton.vueファイルを作成します。
<!-- src/components/MyButton.vue -->
<template>
<button :class="{ active: isActive }" @click="handleClick">{{ label }}</button>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
type Props = {
label: string
isActive?: boolean
}
defineProps<Props>()
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click')
}
</script>
テストの作成
次に、src/components/__tests__ディレクトリに移動し、MyButton.test.tsファイルを作成します。
// src/components/__tests__/MyButton.test.ts
import { mount } from '@vue/test-utils'
import MyButton from '../MyButton.vue'
import { describe, it, expect } from 'vitest'
describe('MyButton', () => {
it('ラベルがレンダリングされる', () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me' }
})
expect(wrapper.text()).toBe('Click Me')
})
it('isActive が true のときに active クラスが適用される', () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me', isActive: true }
})
expect(wrapper.classes()).toContain('active')
})
it('クリックイベントが発火する', async () => {
const wrapper = mount(MyButton, {
props: { label: 'Click Me' }
})
await wrapper.trigger('click')
expect(wrapper.emitted()).toHaveProperty('click')
})
})
テストの実行
最後に、テストを実行してみましょう。
$ npm install
$ npm run test:unit
これで、MyButtonコンポーネントの基本的なテストが作成できました。このテストでは、以下の点を確認しています。
- ラベルが正しくレンダリングされること
isActiveプロパティがtrueのときにactiveクラスが適用されること- ボタンがクリックされたときに
clickイベントが発火すること
このテストでは、コンポーネントのレンダリングやイベントハンドリングなど、基本的な動作が期待通りに行われることを確認できます。
6. まとめ
ここまで、Vue3とVitestを使用したコンポーネントテストの基本について説明してきました。クロスマートでは、実際にVitestを用いてこのようなコンポーネントテストを記述しています。
インプットとアウトプットの視点
フロントエンドのコンポーネントは、インプット(入力)とアウトプット(出力)の関数として考えることができます。この視点を持つことで、テストすべきポイントが明確になり、効率的にテストを進めることができます。
小さく作ることの重要性
まずは、小さなコンポーネントから始めることが重要です。小さなコンポーネントはテストしやすく、変更の影響を最小限に抑えることができます。小さく作ることで、テストのメリットを実感できるようになるでしょう。無理にすべてをテストしようとせず、まずは書けるところから始めることが大切です。それが続けるコツです。
この記事が、コンポーネントテストの基本を理解し、実践するための助けになれば幸いです。今後も継続的にテストを書き、より良いコード品質を維持していきましょう。
最後に
最後まで読んでいただいき、ありがとうございます。
クロスマートでは、フロントエンドエンジニアを含むエンジニアやプロジェクトマネージャー(PM)、デザイナーなど幅広いポジションで仲間を募集しています。技術に情熱を持ち、共に成長していきたいという方は、ぜひご連絡ください!