【Nuxt3】Vuetify3 + Vitestで単体テスト実行環境を構築
フロントエンドエンジニアのハコザキです。
今回は、Nuxt3 + Vuetify + Vitestでの単体テストの実行環境を構築してみたいと思います!
はじめに
本記事では、昨年の11月に安定版がリリースされたNuxt3に、 VueのUIライブラリであるVuetify、ViteベースのテスティングフレームワークであるVitestを使用して単体テストの実行環境を構築します。
具体的には以下の流れで進めていきます!
- Nuxt3 + Vuetify3の構築
- Vitestでのテスト実行環境導入方法
- Vue用のテストライブラリの導入、Vueコンポーネントの単体テストの実行
本環境の各バージョンについて
- Node 18.13.0
- pnpm 8.9.2
- Nuxt 3.8.0
- Vue 3.3.7
- Vuetify 3.3.12
- Vitest 0.34.1
※ベータ版として1系がリリースされておりますが、今回は使用しません。 - @vue/test-utils 2.4.1
Nuxt3 + Vuetifyの環境構築
Nuxt3プロジェクトの作成
以下のコマンドを実行します。
pnpm dlx nuxi@latest init nuxt3-vuetify-vitest
cd nuxt3-vuetify-vitest/
pnpm run dev
ターミナルに Nuxt project has been created with the v3 template. ~~
が表示されていればokです。
localhost:3000 にアクセスし、Nuxt3の初期画面が表示されていれば Nuxt3の初期環境構築はokです!
Vuetify3の導入、初期設定
続いてVuetify3をインストールします
pnpm i -D vuetify vite-plugin-vuetify
続いて、 nuxt.config.ts
にVuetifyの設定を記載します。
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
build: {
transpile: ['vuetify'],
},
modules: [
(_options, nuxt) => {
nuxt.hooks.hook('vite:extendConfig', (config) => {
// @ts-expect-error
config.plugins.push(vuetify({ autoImport: true }))
})
},
//...
],
vite: {
vue: {
template: {
transformAssetUrls,
},
},
},
})
Viteの設定を拡張し、Vuetifyコンポーネントのオートインポート化を行っています。
また、VImgなどのコンポーネントので相対パスを通すようにする設定などを追加しました。
続いて、pluginsフォルダにVuetifyの設定ファイルを作成します。plugins/vuetify.ts
を作成し以下の記述をします。
import { createVuetify } from 'vuetify'
export default defineNuxtPlugin((nuxtApp) => {
const vuetify = createVuetify()
nuxtApp.vueApp.use(vuetify)
})
Nuxt3からplugins内のファイルはAutoImportになったため、そのまま適用されるようになります。
※ Nuxt2ではnuxt.config.tsでファイルを指定する必要がありました
続いて、layouts/default.vue
を作成し、以下を記述します。
<template>
<VApp>
<slot />
</VApp>
</template>
app.vue
を以下のように編集します。
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
ここまででNuxt3のプロジェクトでVuetifyのコンポーネントが使えるようになりました。
続いてテスト用のコンポーネントを作成します。
今回はすごく単純な、クリックするとボタンラベルの値が1増えるコンポーネントを作成します。
<script setup lang="ts">
const counter = ref(0)
const increment = () => {
counter.value++
}
</script>
<template>
<VBtn variant="flat" color="primary" @click="increment">{{ counter }}</VBtn>
</template>
トップページ用ファイルを作成し、作成したコンポーネントが表示されているかどうか
実際に呼び出してみて確認します。
pages/index.vue
を作成し、以下を記述します。
<template>
<VContainer>
<IncrementButton />
</VContainer>
</template>
この状態で pnpm run dev
を実行し localhost:3000 にアクセスします。
以下のようにVuetifyのボタンコンポーネントが表示されていればNuxt3 + Vuetifyの設定はokです!!
単体テスト実行環境の構築
続いて、単体テストの実行環境の構築を行います。
Vitestの導入
VitestはVite環境で動作するテスティングフレームワークです。
よく聞くテスティングフレームワークとしてJestがありますが、
Nuxt3は標準でVite環境のため、Vitestでのテストできるように構築してみたいと思います!
公式サイト:https://vitest.dev/
以下のコマンドでVitest
、@vue/test-utils
をインストールします。@vue/test-utils
はVue.js 向けの公式単体テストライブラリです。
公式サイト:https://test-utils.vuejs.org/
pnpm i -D vitest @vue/test-utils happy-dom
続いてNuxt3のプロジェクトルート直下にvitest.config.tsを作成し、
Vitestの設定を記述します。
import { defineConfig } from 'vitest/config'
export default defineConfig({})
npm scriptsに "test": "vitest"
を追加します。
{
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"test": "vitest"
},
}
この状態で pnpm run test を実行し、以下の表示になれば
Vitestでのテストができる状態になってます。
pnpm run test
> nuxt-app@ test /Users/〇〇〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
> vitest
DEV v0.34.6 /Users/〇〇〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
include: **/*.{test,spec}.?(c|m)[jt]s?(x)
exclude: **/node_modules/**, **/dist/**, **/cypress/**, **/.{idea,git,cache,output,temp}/**, **/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*
watch exclude: **/node_modules/**, **/dist/**
No test files found, exiting with code 1
ELIFECYCLE Test failed. See above for more details.
この状態ではテストファイルが存在しないため、No test files found
と表示されます。
Vitestでのテストは実行できている状態なので、
実際にテストファイルを作成してみます。
vitest.config.ts
に以下を記述します。
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
environment: 'happy-dom',
server: {
deps: {
inline: ['vuetify'],
},
},
},
})
※ Vuetify公式でも単体テストの構築方法が紹介されています
https://vuetifyjs.com/en/getting-started/unit-testing/#using-vite
このままでもテストの実行は可能ですが、以下のようなWarningが表示されます。
そのため、上記のような書き方にしています。
Vitest "deps.inline" is deprecated. If you rely on vite-node directly, use "server.deps.inline" instead. Otherwise, consider using "deps.optimizer.web.include"
tsconfig.jsonの compilerOptions
を追加します。
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"types": ["vitest/globals"]
}
}
test.global
をtrue 、tsconfig
を上記のように編集することで、
各テストファイルでVitestの describe
や test
といったメソッドを
importせずに使うことができます。
Vueコンポーネントの単体テスト
ここまではテストの実行環境の整備を行ってきました。
では、実際にVueコンポーネントのテストファイルを作成し、
Vitestでのテストを実行してみたいと思います!
test
フォルダを作成し、その中にcomponents
フォルダを作成します。
続いてその中に IncrementButton.spec.ts
を作成し以下を記述します。
まずはテストできるかの確認のため、
コンポーネントがレンダリングできているかのテストのみ書いてます!
import IncrementButtonVue from '~/components/IncrementButton.vue'
import { mount } from '@vue/test-utils'
describe('IncrementButton.vue', () => {
test('表示', () => {
const wrapper = mount(IncrementButtonVue)
expect(wrapper.exists()).toBeTruthy()
})
})
この状態で以下のコマンドでVitestでのテストを実行してみます。
pnpm run test
以下のようにcomponentsフォルダ内のVueコンポーネントが
importできていないエラーが表示されてしまい、テストが実行できていません..
pnpm run test
> nuxt-app@ test /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
> vitest
DEV v0.34.6 /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
❯ test/components/IncrementButton.spec.ts (0)
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
FAIL test/components/IncrementButton.spec.ts [ test/components/IncrementButton.spec.ts ]
Error: Failed to resolve import "~/components/IncrementButton.vue" from "test/components/IncrementButton.spec.ts". Does the file exist?
❯ formatError node_modules/.pnpm/vite@4.5.0_@types+node@20.8.9/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:44062:46
❯ TransformContext.error node_modules/.pnpm/vite@4.5.0_@types+node@20.8.9/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:44058:19
❯ normalizeUrl node_modules/.pnpm/vite@4.5.0_@types+node@20.8.9/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:41844:33
❯ async file:/Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest/node_modules/.pnpm/vite@4.5.0_@types+node@20.8.9/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:41998:47
❯ TransformContext.transform node_modules/.pnpm/vite@4.5.0_@types+node@20.8.9/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:41914:13
❯ Object.transform node_modules/.pnpm/vite@4.5.0_@types+node@20.8.9/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:44352:30
❯ loadAndTransform node_modules/.pnpm/vite@4.5.0_@types+node@20.8.9/node_modules/vite/dist/node/chunks/dep-bb8a8339.js:55026:29
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests no tests
Start at 18:23:39
Duration 277ms (transform 11ms, setup 1ms, collect 0ms, tests 0ms, environment 95ms, prepare 57ms)
FAIL Tests failed. Watching for file changes...
press h to show help, press q to quit
このエラーを解決するには、
Vitestの設定にコンポーネントの読み込み設定を追加する必要があります。
まずは以下のコマンドで必要となるライブラリーをインストールします。
pnpm i -D unplugin-vue-components unplugin-auto-import
続いて、 vitest.config.ts
を編集します。
新たにplugins, resolve
を追加しました。
コンポーネントのパスを指定し読み込み設定や、
refなどのVueの機能を使えるようにするなどの設定を追加しております。
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import path from 'path'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue'],
}),
Components({
dirs: ['components'],
}),
],
resolve: {
alias: {
'~': path.resolve(__dirname, './'),
},
},
test: {
globals: true,
environment: 'happy-dom',
server: {
deps: {
inline: ['vuetify'],
},
},
},
})
この状態で再度テストを実行すると先程のエラーは解消されると思います..!!
テストの結果を確認するとokになっていますが、Warningが表示されてしまっています。
pnpm run test
> nuxt-app@ test /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
> vitest
DEV v0.34.6 /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
stderr | test/components/IncrementButton.spec.ts > IncrementButton.vue > 表示
[Vue warn]: Failed to resolve component: VBtn
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
at <IncrementButton ref="VTU_COMPONENT" >
at <VTUROOT>
✓ test/components/IncrementButton.spec.ts (1)
✓ IncrementButton.vue (1)
✓ 表示
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 18:46:14
Duration 390ms (transform 63ms, setup 0ms, collect 89ms, tests 8ms, environment 103ms, prepare 61ms)
PASS Waiting for file changes...
press h to show help, press q to quit
Vuetifyのボタンコンポーネント(VBtn)が読み込まれていない状態でテストを実行しているようなので、VuetifyコンポーネントをVitestに対して読み込ませる必要があります。
[Vue warn]: Failed to resolve component: VBtn
再度 vitest.config.ts
を編集します。
ここまでのvitest.config.ts
の全体ファイルを以下の通りです。
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import path from 'path'
import vuetify from 'vite-plugin-vuetify'
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue'],
}),
Components({
dirs: ['components'],
}),
vuetify({
autoImport: true,
}),
],
resolve: {
alias: {
'~': path.resolve(__dirname, './'),
},
},
test: {
globals: true,
environment: 'happy-dom',
server: {
deps: {
inline: ['vuetify'],
},
},
setupFiles: path.resolve(__dirname, './test/setup.ts'),
},
})
test/setup.ts
を作成し、以下を記述します。
import { config } from '@vue/test-utils'
import { createVuetify } from 'vuetify'
const vuetify = createVuetify()
config.global.plugins = [vuetify]
この状態で再度テストを実行してみると、先程のWarningは表示されていないと思います!
テストファイル内で実際にVuetifyコンポーネントがレンダリングできているか
試してみたいと思います。
test/components/IncrementButton.spec.ts
にconsole.log
を追加しました。
import IncrementButtonVue from '~/components/IncrementButton.vue'
import { mount } from '@vue/test-utils'
describe('IncrementButton.vue', () => {
test('表示', () => {
const wrapper = mount(IncrementButtonVue)
console.log(wrapper.html())
expect(wrapper.exists()).toBeTruthy()
})
})
再度テスト実行すると、以下のようにHTMLタグが出力されていると思います!
pnpm run test
> nuxt-app@ test /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
> vitest
DEV v0.34.6 /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
stdout | test/components/IncrementButton.spec.ts > IncrementButton.vue > 表示
<button type="button" class="v-btn v-theme--light bg-primary v-btn--density-default v-btn--size-default v-btn--variant-flat"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
<!----><span class="v-btn__content" data-no-activator="">0</span>
<!---->
<!---->
</button>
✓ test/components/IncrementButton.spec.ts (1)
✓ IncrementButton.vue (1)
✓ 表示
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 19:23:37
Duration 747ms (transform 328ms, setup 304ms, collect 108ms, tests 16ms, environment 126ms, prepare 71ms)
PASS Waiting for file changes...
press h to show help, press q to quit
Vuetifyのコンポーネントが正しくレンダリングされ、
テストが実行できることが確認できました!!
続いてボタンコンポーネントに対して、単体テストを追加してみます
import IncrementButtonVue from '~/components/IncrementButton.vue'
import { mount } from '@vue/test-utils'
describe('IncrementButton.vue', () => {
test('表示', () => {
const wrapper = mount(IncrementButtonVue)
expect(wrapper.exists()).toBeTruthy()
})
test('カウントが0から始まる', () => {
const wrapper = mount(IncrementButtonVue)
expect(wrapper.text()).toContain('0')
})
test('ボタンクリックでカウントが増える', async () => {
const wrapper = mount(IncrementButtonVue)
const button = wrapper.find('button')
await button.trigger('click')
expect(wrapper.text()).toContain('1')
})
})
find
やtrigger
といったテスト用メソッドも問題なく動作しています。
pnpm run test
> nuxt-app@ test /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
> vitest
DEV v0.34.6 /Users/〇〇/Desktop/work/demo/nuxt3-vuetify-vitest
✓ test/components/IncrementButton.spec.ts (3)
✓ IncrementButton.vue (3)
✓ 表示
✓ カウントが0から始まる
✓ ボタンクリックでカウントが増える
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 19:26:52
Duration 679ms (transform 303ms, setup 262ms, collect 110ms, tests 20ms, environment 92ms, prepare 58ms)
PASS Waiting for file changes...
press h to show help, press q to quit
これでNuxt3プロジェクト内で、
Vuetify製のVueコンポーネントの単体テスト環境の構築が完了しました!
まとめ
この記事では、Nuxt3プロジェクト内でVuetify3とVitestを使用して単体テスト環境を構築する方法を解説しました。
Vitestでのテスト実行時のcomponentsフォルダ内のコンポーネントやVuetifyコンポーネントの読み込み部分は少々手間ですが、手動で一つ一つimprtする手間が省けるので
Nuxt3+Vuetifyのプロジェクトに対しテストコードを書く場合はぜひ設定していただきたいです!!!