[vitest]Vuetify を Vitest（Vue 3）でテストする

## この記事でやること

- `Vitest` で Vuetify のコンポーネントを使ったテストが動くようにする
- `vitest-setup.ts` を作って、共通のテスト設定をまとめる

## はじめに
Vue 3 のアプリで Vuetify を使っているコンポーネントをテストしようとした時、何にも考えずテストを書いたところ、こんなエラーが出てきたところが始まりでした。
```
TypeError: Unknown file extension ".css" for /usr/src/node_modules/vuetify/lib/directives/ripple/VRipple.css
```
ふむふむ`.css`なんて知らないぞと・・・
そこから少し手間取ってしまったので奮闘記として残します！

## まずは全体

#### テスト全体でVuetifyが使えるようにvitest-setup.tsを作成

```typescript:vitest-setup.ts(Vuetify用セットアップファイル)
import { config } from "@vue/test-utils";
import ResizeObserver from "resize-observer-polyfill";
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";

// Vuetifyインスタンスを作成
const vuetify = createVuetify({
	components,
	directives,
});

// Vue Test Utilsのglobal設定にプラグインとして追加
config.global.plugins = (config.global.plugins || []).concat([vuetify]);

// ResizeObserverのpolyfill
window.ResizeObserver = ResizeObserver;
```

#### `vitest.config.ts`に`setupFiles`と`inline`の項目を追加し作成ファイルを指定

```typescript:vitest.config.ts(vitest用セットアップファイル)
import { fileURLToPath } from "node:url";
import { configDefaults, defineConfig, mergeConfig } 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)),
			globals: true,
			setupFiles: ["./vitest-setup.ts"], // 追加
			server: { // 追加
				deps: {
					inline: ["vuetify"],
				},
			},
		},
	}),
);
```

#### このようなレイアウト用コンポーネントのテストを想定

```typescript:TestPage.vue(対象コンポーネント)
<script setup lang="ts">
const props = defineProps({
  testText: {
    type: String,
    default: "test1234",
  },
});
</script>

<template>
  <v-app-bar> hogehoge </v-app-bar>
  <v-main>
    <v-container>
      <h1>{{ testText }}</h1>
    </v-container>
  </v-main>
  <v-footer app color="primary"> fugafuga </v-footer>
</template>
```

```typescript:TestPage.spec.ts(テストファイル)
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import TestPage from "../TestPage.vue";

describe("testComponent", async () => {
	it("should render", async () => {
		const wrapper = mount({
			template: "<v-layout><TestPage :test-text='testText'/></v-layout>",
			components: {
				TestPage,
			},
			props: {
				testText: "test123",
			},
		});
		expect(wrapper.html()).toContain("test123");
	});
});
```



## 1. configファイルに追記

公式を見てみたところvuetifyの依存関係をインライン化しなきゃということで、テスト用のconfigファイル`vitest.config.ts`に設定を追加する模様。

https://vuetifyjs.com/ja/getting-started/unit-testing/#vite30924f7f304658345408

```typescript:vitest.config.ts
  test: {
    globals: true,
    environment: 'jsdom',
    server: {
      deps: {
        inline: ['vuetify'],
      },
    },
  },
})
```

## 2. ResizeObserverパッケージ追加
公式ドキュメントにも書かれていますが、jsdom では ResizeObserver がサポートされていないため、別途パッケージを追加します。

```
npm install resize-observer-polyfill --save-dev
```

## 3. いざテスト
テストの対象はVuetifyのレイアウト用のコンポーネントなども使っているものです。
```typescript
<script setup lang="ts">
const props = defineProps({
  testText: {
    type: String,
    default: "test1234",
  },
});
</script>

<template>
  <v-app-bar> hogehoge </v-app-bar>
  <v-main>
    <v-container>
      <h1>{{ testText }}</h1>
    </v-container>
  </v-main>
  <v-footer app color="primary"> fugafuga </v-footer>
</template>
```

こんな感じでテスト！
JSDOM 環境で`vite-plugin-vuetify` の `autoImport: true 設定`は動かないようなので、`components`と`directives`を全てインポートします。

```typescript
import { mount } from "@vue/test-utils";
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";
const vuetify = createVuetify({
	components,
	directives,
});
global.ResizeObserver = require("resize-observer-polyfill");

describe("testComponent", async () => {
	it("should render", async () => {
		const wrapper = mount(testComponent, {
			props: {
				testText: "test123",
			},
			global: {
				plugins: [vuetify],
			},
		});
		expect(wrapper.html()).toContain("test123");
```

残念ながらこのままではエラーとなりました。
レイアウト用コンポーネントなどは公式サイトに記載通り`v-layout`でラップする必要がありました。

```
Error: [Vuetify] Could not find injected layout
```

ということで、v-layoutでラップします。

```typescript
const wrapper = mount(
    {
        template: "<v-layout><TestPage :test-text='testText'/></v-layout>",
        components: {
            TestPage,
        },
        props: {
            testText: "test123",
        },
    },
    {
        global: {
            plugins: [vuetify],
        },
    },
);
expect(wrapper.html()).toContain("test123");
```

これで無事テスト成功しました👏👏👏

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680973/42e8dae4-1ff9-4ee8-8596-3b51b91aacc9.png)


## 4. `vitest-setup.ts` を作成

テストが作成出来るようになりましたが、毎回`createVuetify`や`plugins: [vuetify]`を記述するのは少々面倒。
そこで、以下の内容を`vitest-setup.ts`に切り出します。
- Vuetify のコンポーネント・ディレクティブを読み込み
- Vue Test Utils の config.global.plugins に vuetify を追加
- ResizeObserverに`resize-observer-polyfill`を設定

```typescript:vitest-setup.ts
import { config } from "@vue/test-utils";
import ResizeObserver from "resize-observer-polyfill";
import { createVuetify } from "vuetify";
import * as components from "vuetify/components";
import * as directives from "vuetify/directives";

// Vuetifyインスタンスを作成
const vuetify = createVuetify({
	components,
	directives,
});

// Vue Test Utilsのglobal設定にプラグインとして追加
config.global.plugins = (config.global.plugins || []).concat([vuetify]);

// ResizeObserverのpolyfill
window.ResizeObserver = ResizeObserver;
```

作成したファイルを`vitest.config.ts`に`setupFiles`として設定します。


```typescript:vitest.config.ts
import { fileURLToPath } from "node:url";
import { configDefaults, defineConfig, mergeConfig } 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)),
			globals: true,
			setupFiles: ["./vitest-setup.ts"], // 追加
			server: {    // 追加
				deps: {
					inline: ["vuetify"],
				},
			},
		},
	}),
);
```

これで、Vuetifyを使ったコンポーネントも簡単にテスト出来るようになりました。



## 終わりに
- やっと少しずつVueに慣れてきました
- 今後もテストなど少しずつ残していきます