Vitest / svelte-testing-library を使いSvelteのコンポーネントテストする方法

# こんな方にマッチする記事です！
- svelteのテストコードを書くのに興味がる方
- svelteでコンポーネントテストを行う方法を知りたい方
- testing-libraryのsvelte版にはどのような機能があるか知りたい方

### 前提
- すでにSvelteとViteは導入済の前提となります。
- svelteのversionは4系を利用しています。


## ライブラリのインストール
```
yarn add --dev vitest
```
 - Vitestは、Viteに最適化された高速なテストランナーです。モダンなJavaScriptやTypeScriptプロジェクトでのユニットテストや統合テストを効率的に実行できます。

https://vitest.dev/
```
yarn add --dev @testing-library/svelte
```
- @testing-library/svelteは、Svelteコンポーネントのテストを支援するライブラリです。ユーザーの視点に立ったテストを可能にし、コンポーネントのレンダリングやユーザーインタラクションを簡単に検証できます。

https://testing-library.com/docs/svelte-testing-library/intro/


```
yarn add --dev @testing-library/jest-dom
```
  - @testing-library/jest-domは、Jest(vitest)と組み合わせてDOM要素のアサーションを拡張するライブラリです。より直感的で読みやすいテストコードを書くためのカスタムマッチャーを提供します。
  - @testing-library/svelteの公式doc内にてもこちらのライブラリの使用の推奨がされています

```
yarn add --dev @testing-library/user-event
```
  - @testing-library/user-eventはテスト環境でユーザーの操作をシミュレートするためのライブラリです。これにより、クリック、入力、キーボード操作などのイベントを模倣し、ユーザーインターフェースの動作をテストすることができます。
    
https://github.com/testing-library/user-event

```
yarn add --dev jsdom
```
  - jsdomは、Node.js環境でブラウザのDOMをシミュレートするライブラリです。ブラウザ専用のAPIをサーバーサイドで利用可能にし、フロントエンドのテストやスクレイピングに役立ちます。

https://github.com/jsdom/jsdom




## Viteの設定
- vite.config.ts ファイルを開き、Vitestの設定を追加します。

```ts:vite.config.ts
import { defineConfig } from 'vite'
import { svelte } from '@vitejs/plugin-svelte'

export default defineConfig({
  test: {
    root: __dirname,
    globals: true, // テスト環境でグローバル変数（例：describe, it, expect）を有効にします。これにより、各テストファイルでこれらの関数をインポートせずに使用できます。
    environment: "jsdom",// テストを実行する環境を jsdom に設定しています。
    setupFiles: './src/setupTests.ts',
    testTimeout: 10000,
    include: ["src/**/*.test.ts"],
  },
})
```

## tsconfig.jsonの設定
```
{
  "compilerOptions": {
    // その他の設定項目...
    "types": ["vitest/globals", "@testing-library/jest-dom"]
  }
}
```

- `vitest/globals`
    - itest を使用する際に、describe、it、expect などのグローバルなテスト関数を型チェックおよび自動補完で認識させるための型定義です


- `@testing-library/jest-dom`
    - @testing-library/jest-dom を使用する際に、カスタムマッチャー（例：toBeInTheDocument、toHaveTextContent など）を TypeScript に認識させるための型定義です。


## setupTests.tsの作成
```ts:setupTests.ts
import "@testing-library/jest-dom";

// テスト全体に共通して事前に設定したい内容をここでimportしたり、設定などします
// dayjsのlocale設定や汎用的なmockやスタブの定義をここでしたりします
```
### setupTests.ts の役割とは
#### グローバル設定の適用:
- テスト環境全体で共通して使用する設定や初期化処理をここで行います。
例として、@testing-library/jest-dom をインポートしてカスタムマッチャー（toBeInTheDocument など）をグローバルに利用可能にすることが挙げられます。
#### モックやスタブの設定:
- 外部モジュールやAPIのモックをここで設定することで、テストの際に依存関係を制御できます。
#### 初期化処理の実行:
- グローバルな状態管理やライブラリの初期化（例: svelte-i18n のロケール設定）を行うことで、各テストファイルでの初期化コードの重複を避けます。

## tsconfig.jsonのcompilerOptions.types に "vitest" を追加して、Vitest のグローバル関数の型を認識させる
```json:tsconfig.json
"compilerOptions": {

...//省略
 "types": ["vitest/globals", "@testing-library/jest-dom"]

}
```








# テストコードサンプル
このサンプルコードでは下記2点をテストしています。
- 「画面初期表示時にはボタンが表示されていて、Hello~~の文言が表示されていないこと」
- 「ボタンをクリックするとHello~~の文言が表示されること」

```svelte:greeter.svelte
<script>
  export let name

  let showGreeting = false

  const handleClick = () => (showGreeting = true)
</script>

<button on:click="{handleClick}">Greet</button>

{#if showGreeting}
<p>Hello {name}</p>
{/if}
```




```ts:greeter.test.ts
import {render, screen} from '@testing-library/svelte'
import userEvent from '@testing-library/user-event'
import {expect, test} from 'vitest'

import Greeter from './greeter.svelte'

test('no initial greeting', () => {
  render(Greeter, {name: 'World'})

  const button = screen.getByRole('button', {name: 'Greet'})
  const greeting = screen.queryByText(/hello/iu)

  expect(button).toBeInTheDocument()
  expect(greeting).not.toBeInTheDocument()
})

test('greeting appears on click', async () => {
  const user = userEvent.setup()
  render(Greeter, {name: 'World'})

  const button = screen.getByRole('button')
  await user.click(button)
  const greeting = screen.getByText(/hello world/iu)

  expect(greeting).toBeInTheDocument()
})
```



- `render`
    - 指定したコンポーネントをレンダリングします。この際に必須のpropsを渡す必要があります。
- `screen`
    - レンダリングしたコンポーネントに対してDOM操作を行います。
    - `getByRole`や`getByText`などの方法で意図したDOM操作を行います。
    - screen操作には多数の操作関数が用意されています。詳しくは[こちら](https://testing-library.com/docs/dom-testing-library/cheatsheet/#queries)を確認してみてください。とりあえずは`getBy~~`と`queryBy~~`はよく使い便利なのでおすすめです。
- `userEvent` 
    - ユーザーのインタラクションをシミュレートするために使います。userEventを使用することで、ユーザーがブラウザ上で実際に行う操作（クリック、入力、キー操作など）を模擬し、より現実的なテストを記述することができます。



# テスト実行

```json:package.json
"scripts": {
 "test": "vitest"
}
```

- 上記の設定を行えば`yarn run test` を実行でテストが実行されるようになります。



# GitHubActionsのCIにテスト実行を組み込む


今回はwebディレクトリ配下にpackage.jsonが配置されてい場合のコードになります。
皆さんのディレクトリ構造に応じてwebの部分の記述は変えてください

下記の設定の場合「PRを作成した場合」、「mainブランチのコードが変更になった時」にvitestのテストのCIが動きます。

```yml:ci.yml
name: CI

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  vitest:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: yarn
          cache-dependency-path: web/yarn.lock
      - name: Install dependencies
        run: |
          cd web
          yarn install
      - name: Run vitest
        run: |
          cd web
          yarn run test

```

# おすすめvscode拡張機能
vscodeを使った開発をしている方はvitestの拡張機能を利用することをお勧めします。

https://marketplace.visualstudio.com/items?itemName=vitest.explorer

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617821/763e83cd-6416-94bd-d85f-af5d00d4b9ff.png)


### こんな感じでサイドバーから対象のテストだけをクリックで実行できたり
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617821/442442c9-da6e-6f9c-a421-00cad9f83bc9.png)


### こんな感じでテストコード上から対象のテストだけをクリックで実行できてとっても便利
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/617821/a0232a4d-2b80-d218-5983-39f67ee53b39.png)




## 最後に
今回はsvelteのコンポーネントテストをメインに扱いました。

Vitest / svelte-testing-libraryの組み合わせることにより、ユーザーイベントやDOM操作の関数が豊富に使え、より柔軟で詳細なテストケースを書くことができるようになります。

「ここだけはどうしてもコンポーネントテストしたい！」みたいな箇所でコンポーネントテストを実施するのに大きなバリューを発揮してくれるツールだと思うので、是非試してみてください！