Vitest は Vite 環境で利用することができる Unit Testing のフレームワークです。Vite を利用していため Vite と同様に高速で動き、TypeScript なども追加設定なしで利用できることが特徴です。また Unit Testing に Jest を経験したことがあれば同様の関数でテストを実施ことができるのでこれまでの知識も活用することができます。Vue, Svelte などのフレームワークでは公式のプロジェクト作成ツールでプロジェクトを作成すると Unit Testing のツールとして Vitest を利用するかどうか聞かれるので開発者にとって知っていて当たり前の知識になってきていることもわかります。Vite と同様にすごい勢いで利用者が拡大しているのでまだ利用したことがないのであればぜひこの機会に Vitest にチェレンジしてみてください。

本文章 Vanilla JavaScript で Vitest の動作確認を行い、その後 Vue, React, Svelte のプロジェクトで Vitest を利用してシンプルなテストを実行します。

はじめての Vitest

最初は Vanilla JavaScript(フレームワークを利用しない)環境で動作確認を行います。

Vite プロジェクトの作成

Vitest の動作確認を行うために Vite のプロジェクト作成ツール create vite を利用してプロジェクトの作成を行います。コマンドを実行するとプロジェクト名の設定とフレームワークの選択、Variant で JavaScript か TypeScript を選択することができます。フレームワークの選択では”Vanilla JavaScript”を選択して JavaScript で動作確認を行います。


 % npm create vite@latest
✔ Project name: … vite-project
✔ Select a framework: › Vanilla
✔ Select a variant: › JavaScript

Scaffolding project in /Users/mac/Desktop/vite-project...

Done. Now run:

  cd vite-project
  npm install
  npm run dev

Vitestのインストール

ViteをインストールしただけではVitestはインストールされるわけではないのでVitestのインストールを行います。


 % npm install -D vitest 

vitest インストール後に package.json ファイルでインストールした vitest のバージョンを確認しておきます。


{
  "name": "vite-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "vite": "^4.4.5",
    "vitest": "^0.34.6"
  }
}

最もシンプルなテスト

Vitest のインストールができたのでテストを実施してみましょう。プロジェクトフォルダ直下に test フォルダを作成してその下に example.test.js ファイルを作成します。テストファイルである example.test.js には以下のコードを記述します。


import { expect, test } from 'vitest';

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

vitest から import した test 関数の第一引数にはテストがどのような内容なのか説明を記述することができます。後に読み直してもテストの内容がわかるような説明を記述します。第 2 引数の関数の中にテストを記述していきます。テストでは足し算が正しく行われているか確認するためにアサーションを行う必要があります。アサーションには expect 関数を利用して引数には値を指定します。expect 関数の 2+2 の足し算が 4 になることをテストしたいので toBe 関数に 4 を指定しています。toBe 関数は matchers 関数の一つで expect 関数の引数に指定した値が toBe 関数の引数に指定した値と一致するかチェックを行います。

テストのアサーション(Assertion)は、期待される結果と実際の結果を比較しそれらが一致しているかどうかを検証するための文です。

testのaliasにitがあるのでit関数を利用することもできます。


import { expect, it } from 'vitest';

it('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

テストを実行するためにpackage.jsonのscriptプロパティにtestを追加します。


{
  "name": "vitest-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest"
  },
  "devDependencies": {
    "vite": "^4.1.0",
    "vitest": "^0.28.4"
  }
}

package.jsonファイルを更新後、npm run testコマンドを実行してテストを行います。


% npm run test

> vitest-project@0.0.0 test
> vitest

DEV  v0.34.6 /Users/mac/Desktop/vite-project

 ✓ test/example.test.js (1)

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  22:03:07
   Duration  534ms (transform 112ms, setup 0ms, collect 9ms, tests 2ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

メッセージを見ると test/example.test.js ファイルのテストが 1 件パスしたことがわかります。

npm run test はデフォルトで watch モードになっているため example.test.js ファイルを更新すると自動でテストが再実行されます。試しに toBe 関数の引数を 4 から 5 に変更すると自動でテストが再実行されます。


import { expect, test } from 'vitest';

test('two plus two is four', () => {
  expect(2 + 2).toBe(5);
});

 RERUN  test/example.test.js x1

 ❯ test/example.test.js (1)
   × two plus two is four

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  test/example.test.js > two plus two is four
AssertionError: expected 4 to be 5 // Object.is equality
 ❯ test/example.test.js:4:17
      2| 
      3| test('two plus two is four', () => {
      4|   expect(2 + 2).toBe(5);
       |                 ^
      5| });
      6| 

  - Expected   "5"
  + Received   "4"

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

 Test Files  1 failed (1)
      Tests  1 failed (1)
   Start at  22:07:05
   Duration  43ms


 FAIL  Tests failed. Watching for file changes...
       press h to show help, press q to quit

テストは失敗しますがどこにエラーが発生したかもわかるようなメッセージになっています。エラー時のメッセージもわかったので変更した toBe の値は 5 から 4 に戻しておきます。

package.json ファイルの script に test を追加しましたが npx vitest コマンドでもテストを実行することができます。


% npx vitest

Coverage c8インストール

コードカバレッジのツールとして c8 と istanbul をサポートしていますがデフォルトでは c8 が利用されます。 テストのコードカバレッジ(Code Coverage)は、実行したテストがソースコードのどれくらいの範囲を対象としているかを測定することができます。実際にシンプルな例を利用してコードカバレッジがどのようなものか確認していきます。

コードカバレッジを確認するために package.json の script に coverage の追加を行います。


{
{
  "name": "vite-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest",
    "coverage": "vitest run --coverage"
  },
  "devDependencies": {
    "vite": "^4.4.5",
    "vitest": "^0.34.6"
  }
}

package.jsonファイルを更新後、npm run coverageコマンドを実行します。初めてコマンドを実行する際はcoverage-c8をインストールするか聞かれるので”y”を選択します。


% npm run coverage

> vitest-project@0.0.0 coverage
> vitest run --coverage

 MISSING DEP  Can not find dependency '@vitest/coverage-v8'

✔ Do you want to install @vitest/coverage-v8? … yes

added 35 packages, and audited 93 packages in 4s

18 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Package @vitest/coverage-v8 installed, re-run the command to start.

package.jsonファイルには@vitest/coverage-c8が追加されます。

npm run coverageコマンドを再度実行するとカバレッジレポートが表示されますがすべて0になっています。


 % npm run coverage

> vitest-project@0.0.0 coverage
> vitest run --coverage


RUN  v0.34.6 /Users/mac/Desktop/vite-project
      Coverage enabled with v8

 ✓ test/example.test.js (1)
   ✓ two plus two is four

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  15:08:15
   Duration  431ms (transform 29ms, setup 0ms, collect 12ms, tests 4ms, environment 0ms, prepare 95ms)

 % Coverage report from v8
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |       0 |        0 |       0 |       0 |
----------|---------|----------|---------|---------|-------------------

Stmts は Statement Coverage, Branch は Branch Coverage, Funcs は Function Coverage, Lines は Line Coverage を表しています。

カバレッジレポートの値を表示させるためにプロジェクトディレクトリ直下に multiply.js ファイルを作成します。引数に 2 つの数字を取り掛け合わせるだけのコードです。


export default function multiply(num1, num2) {
  let result = num1 * num2;
  return result;
}

multiply関数のテストを行います。example.test.jsファイルを開いて新たにtest関数を追加します。


import { expect, test } from 'vitest';
import multiply from '../multiply';

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

test('takes two numbers as arguments and multiplies them', () => {
  expect(multiply(2, 3)).toBe(6);
});

npm run coverageコマンドを実行すると先ほどとは異なり2つのテストにパスし、カバレッジレポートの値が100%になっていることが確認できます。


RUN  v0.34.6 /Users/mac/Desktop/vite-project
      Coverage enabled with v8

 ✓ test/example.test.js (2)

 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  22:52:00
   Duration  727ms (transform 102ms, setup 0ms, collect 15ms, tests 2ms)

 % Coverage report from c8
-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files    |     100 |      100 |     100 |     100 |
 multiply.js |     100 |      100 |     100 |     100 |
-------------|---------|----------|---------|---------|-------------------

カバレッジの数値がどのような条件で変化するの確認するためにmultiply.jsファイルに条件分岐を入れます。


export default function multiply(num1, num2) {
  let result = num1 * num2;
  if (result < 10) {
    return true;
  } else {
    return false;
  }
}

同じテストを実行しますがtestの説明を変更しています。


import { expect, test } from 'vitest';
import multiply from '../multiply';

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

test('two numbers multiply less than 10', () => {
  expect(multiply(2, 3)).toBeTruthy();
});

npm run coverage コマンドを実行するとテストにパスしましたが数値が変わっていることがわかります。理由は条件分岐を入れたことでコードの一部が実行されなくなったためです。Uncovered Line の 6-7 は mltiply.js の条件分岐の else の箇所なのでその部分がテストで確認できていないことがカバレッジレポートからわかります。


 Test Files  1 passed (1)
      Tests  2 passed (2)
   Start at  22:55:16
   Duration  668ms (transform 101ms, setup 0ms, collect 14ms, tests 3ms)

 % Coverage report from c8
-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------|---------|----------|---------|---------|-------------------
All files    |      75 |       50 |     100 |      75 |                   
 multiply.js |      75 |       50 |     100 |      75 | 6-7               
-------------|---------|----------|---------|---------|-------------------

else部分のコードもカバーできるように新たにテストを追加します。


//略
test('two numbers multiply more than 10', () => {
  expect(multiply(4, 3)).toBeFalsy();
});

npm run coverageを実行するとUncovered Lineも消えて100%に戻りました。


 RUN  v0.28.4 /Users/mac/Desktop/vitest-project
      Coverage enabled with c8

 ✓ test/example.test.js (3)

 Test Files  1 passed (1)
      Tests  3 passed (3)
   Start at  22:56:43
   Duration  686ms (transform 107ms, setup 0ms, collect 14ms, tests 3ms)

 % Coverage report from c8
-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files    |     100 |      100 |     100 |     100 |
 multiply.js |     100 |      100 |     100 |     100 |
-------------|---------|----------|---------|---------|-------------------

Coverageを利用することで作成したコードがテストによってどのくらい実行されたかを確認することがわかりました。

Vitest UI

Vitest UI をインストールすることでブラウザからもテストの実行や結果を確認することができます。Vitest UI はオプションなので追加インストールが必要となります。


 % npm i -D @vitest/ui

コマンド(npx vitest —ui)でも実行することができますが package.json の scripts に追加します。


{
  "name": "vite-project",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest",
    "coverage": "vitest run --coverage",
    "test:ui": "vitest --ui"
  },
  "devDependencies": {
    "@vitest/coverage-v8": "^0.34.6",
    "@vitest/ui": "^0.34.6",
    "vite": "^4.4.5",
    "vitest": "^0.34.6"
  }
}

packge.jsonを更新後、npm run test:uiコマンドを実行します。コマンドを実行するとポート51204で開発サーバが起動し、自動でブラウザから起動して Vitest UI の Dashboard が表示されます。Dashboard にはテストの結果が表示されます。

Vitest UIの画面
Vitest UIの画面

サイドメニューのファイルをクリックするとテストの詳細を確認することができます。

example.test.jsファイルのテストの内容
example.test.jsファイルのテストの内容

テストがパスしているので All tests passed in this file と表示されています。意図的にテストを失敗させます。watch モードによりテストは再実行され、ターミナルにもテストの結果が表示されますがブラウザ上でもテストの結果が即座に反映されます。ブラウザ上のボタンをクリックしたりページのリロードが必要になるわけではありません。

テストの再実行
テストの再実行

describe関数について

テストコードでは test 関数の外側に describe 関数を記述することができます。describe 関数の中には複数の test 関数を記述することができます。これまでは 1 つの test 関数のみ実行していたため describe 関数は必要ではありませんが複数の test 関数を実行する場合は describe 関数で個別の test 関数をグループ化することができます。


import { describe, expect, test } from 'vitest';
import multiply from '../multiply';

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

describe('multiply', () => {
  test('two numbers multiply less than 10', () => {
    expect(multiply(2, 3)).toBeTruthy();
  });

  test('two numbers multiply more than 10', () => {
    expect(multiply(4, 3)).toBeFalsy();
  });
});

Vitest UI上でdescribe関数を追加したことによりmultiplyの下に2つのテストがぶらさがった状態で表示させるようになります。

describe関数追加後のVitest UIの画面
describe関数追加後のVitest UIの画面

Vitestの設定

Vitest の設定を行うためにプロジェクトフォルダ直下に vitest.config.ts ファイルを作成します。Vite の設定ファイル vite.config.ts が存在する場合は vite.config.ts の設定を上書きします。vite.config.ts ファイルでも vitest の設定を行うことができます。


import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
  },
});

globals の値を設定することでテストファイルで test, describe, expect などの関数を import しなくても利用できるようになります。

vitest.config.ts の globals で true を設定せず test 関数を利用した場合(import せずに)には”ReferenceError: test is not defined”のエラーが発生します。

フレームワーク/ライブラリでの動作確認

Vue.js, React, Svelte を利用して Vitest の動作確認を行います。

Vue.js

プロジェクトの作成(create-vue)

プロジェクトの作成は”npm create vite@latest”コマンドで行います。コマンドを実行するとプロジェクト名を設定を行い、その後の framework の選択で Vue を選択すると variant の設定で”Customize with create-vue”を選択することができます。プロジェクト名は”vitest-vue”としています。“Customize with create-vue”を選択しなかった場合についても後ほど確認します。


 % npm create vite@latest
✔ Project name: … vitest-vue
✔ Select a framework: › Vue
? Select a variant: › - Use arrow-keys. Return to submit.
    TypeScript
    JavaScript
❯   Customize with create-vue ↗
    Nuxt ↗

“Customeize with craete-vue”を選択すると Vue.js で利用頻度の高い機能の選択を行うことができます。こでは Vitest 機能のみ選択してプロジェクトを作成します。


Vue.js - The Progressive JavaScript Framework

✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/mac/Desktop/vitest-vue...

Done. Now run:

  cd vitest-vue
  npm install
  npm run dev

プロジェクト作成後、メッセージに表示されている通り作成されるプロジェクトディレクトリに移動して npm install, npm run dev コマンドを実行します。開発サーバが起動するのでブラウザで http://localhost:5173 にアクセスすると下記の画面が表示されます。

Vueの初期画面
Vueの初期画面

インストールライブラリの確認

Vitest 機能をインストールした場合にはどのようなライブラリがインストールされているのか確認するために package.json ファイルを開きます。


{
  "name": "vitest-vue",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test:unit": "vitest"
  },
  "dependencies": {
    "vue": "^3.3.4"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.4.0",
    "@vue/test-utils": "^2.4.1",
    "jsdom": "^22.1.0",
    "vite": "^4.4.11",
    "vitest": "^0.34.6"
  }
}

テストに関連するライブラリには vitest の他に vue/test-utils と jsdom がインストールされていることが確認できます。jsdom は DOM を作成したり操作を行う際に利用します。vue/test-utils を利用して Vue のコンポーネントをマウントすることができメソッドを使ってマウントしたコンポーネントを操作することができます。vue/test-utils は Vue を利用する際にのみ利用します。React ライブラリでは testing library がその代わりになります。Vue でも vue/test-utils の代わりに testing library を利用することができます。また jsdom の代わりに happy-dom を利用することができます。

scripts を見ると test:unit で vitest のコマンドを確認することができます。

テストの実行

create-vue コマンドで Vitest for Unit Testing を選択するとテストを実行するために準備が完了しているので scripts に記述されていた test:unit を利用してテストを実行してみましょう。


% npm run test:unit


> vitest-vue@0.0.0 test:unit
> vitest


 DEV  v0.34.6 /Users/mac/Desktop/vitest-vue

 ✓ src/components/__tests__/HelloWorld.spec.js (1)
   ✓ HelloWorld (1)
     ✓ renders properly

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  20:11:29
   Duration  1.18s (transform 79ms, setup 0ms, collect 138ms, tests 21ms, environment 669ms, prepare 123ms)

% npm run test:unit

> vitest-vue@0.0.0 dev
> vite


  VITE v4.1.1  ready in 638 ms

  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

コマンドを実行するとテストが実行され 1 つのテストにパスしていることがわかります。テストのファイルは src/components/__test__フォルダにある HelloWorld.spec.js であることもわかります。

HelloWorld.spec.js ファイルの中身を確認してみましょう。


import { describe, it, expect } from 'vitest'

import { mount } from '@vue/test-utils'
import HelloWorld from '../HelloWorld.vue'

describe('HelloWorld', () => {
  it('renders properly', () => {
    const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
    expect(wrapper.text()).toContain('Hello Vitest')
  })
})

テストは it 関数の中に記述されています。mount 関数でテストを実施したいコンポーネント HelloWorld をマウントしています。HelloWorld は components フォルダにある HelloWorld.vue を import しています。mount にはオプションを設定することができ”Hello Vitest”という値を持つ msg を props で HelloWorld コンポーネントに渡しています。expect の引数ではマウントした HelloWorld のコンポーネントに対して text メソッドを実行してコンポーネントに含まれるテキストのみ取り出し、toContain 関数の引数の文字列”Hello Vitest”が含まれるかをチェックしています。toContain 関数は matchers 関数です。

テストを実行する Hello World コンポーネントに”Hello Vitest”のテキストが含まれているためテストにパスします。

wrapper.text()で表示される内容は console.log を利用して表示することができます。


describe('HelloWorld', () => {
  it('renders properly', () => {
    const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
    console.log(wrapper.text());
    expect(wrapper.text()).toContain('Hello Vitest')
  })
})

npm run test:unitを実行するとwatchモードとなりファイルの更新を検知するとテストを再実行してくれるのでnpm run test:unitのメッセージの中にconsole.logの内容が表示されています。


//略
stdout | src/components/__tests__/HelloWorld.spec.js > HelloWorld > renders properly
Hello Vitest You’ve successfully created a project with Vite + Vue 3
//略

wrapper.text()から表示される内容に”Hello Vitest”を見つけることができます。

happy-domのインストール

vitestはオプションで–domでjsdomかhappy-domを利用することができます。–domに何も設定せずに実行するとhappy-domをインストールするか聞かれます。yesを選択するとhappy-domのインストールが行われます。


 % npx vitest --dom
 MISSING DEP  Can not find dependency 'happy-dom'

? Do you want to install happy-dom? › (y/N)
//略
Package happy-dom installed, re-run the command to start.

package.jsonファイルにもhappy-domがインストールされていることが確認できます。


"devDependencies": {
  "@vitejs/plugin-vue": "^4.0.0",
  "@vue/test-utils": "^2.2.10",
  "happy-dom": "^8.2.6",
  "vite": "^4.1.0",
  "vitest": "^0.28.4"
}

再度npx vitest –domを実行するとエラーは解消してテストを実行することができます。

デフォルトではdomにはNodeが利用されます。domの指定にはコマンドだけではなくファイルでも行うことができます。ファイルの先頭に”// @vitest-environment happy-dom”を追加します。


// @vitest-environment happy-dom
import { describe, it, expect } from 'vitest';

import { mount } from '@vue/test-utils';
import HelloWorld from '../HelloWorld.vue';

describe('HelloWorld', () => {
  it('renders properly', () => {
    const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } });
    expect(wrapper.text()).toContain('Hello Vitest');
  });
});

またViteの設定ファイルであるvite.config.tsフィルにtestプロパティを追加してenvironmentで”happy-dom”を指定することができます。


import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'happy-dom',
  },
});

happy-domとjsdomの違いとしてhappy-domはjsdomよりも高速に動作するようですが利用できないAPIがいくつかあるようです。

globalsパラメータ

Vanilla JavaScriptと同様に設定ファイルでglobalsの値をtrueにするとdescribe, test, expect関数をimportすることなく利用することができます。

React

React では create-react-app を利用してプロジェクトの作成を行うとデフォルトでテストの設定が行われておりプロジェクト作成後からテストを行うことができます。テストには Jest, testing-library が設定されています。本文書では React で Vitest による Unit Testing の方法を確認するため Vite プロジェクト環境に Vitest をインストールして設定を行います。

プロジェクトの作成

Vitest を利用するため npm create vite コマンドで Vite プロジェクトの作成を行います。framework では”React”、variant では JavaScript を選択します。プロジェクト名は”vite-vitest-react”としています。


% npm create vite@latest
✔ Project name: … vite-vitest-react
✔ Select a framework: › React
✔ Select a variant: › JavaScript

Scaffolding project in /Users/mac/Desktop/vite-vitest-react...

Done. Now run:

  cd vite-vitest-react
  npm install
  npm run dev

コマンド実行後に作成される vite-vitest-react フォルダに移動して npm install コマンドを実行します。Vite プロジェクトを作成しても Vitest はインストールされていないので Vitest のインストールを行います


 % npm install -D vitest

React コンポーネントを Vitest でテストを行うためには Vitest 本体以外に testing-library と jsdom をインストールする必要があります。jsdom の代わりに happy-dom を利用することも可能です。


 % npm install -D jsdom @testing-library/react

テストの実行

必要なライブラリのインストールが完了したのでテストファイルを作成します。src フォルダの下に App.test.js ファイルを作成します。App コンポーネントをテストするテストファイルです。


import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import App from './App';

test('renders h1 text', () => {
  render(<App />);
  const headerElement = screen.getByText(/Vite + React/);
  expect(headerElement).toBeInTheDocument();
});

vite.config.tsファイルでjsdomの設定を行います。


import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
  },
});

npx vitestコマンドでテストを実行します。実行すると下記のエラーが発生します。


 % npx vitest
 //略
Error: Parse failure: Unexpected token (7:9)
Contents of line 7:   render(<App />);

テストファイルの拡張子は js ではなく jsx に変更しなければならないためファイル名を App.test.js から App.test.jsx に変更します。

変更後テストを実行するとテストに失敗して以下のエラーが表示されます。


TestingLibraryElementError: Unable to find an element with the text: /Vite + React/. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

文字列の”Vite + React”の”+“をエスケープしていないので文字列を見つけられず screen.getByText の部分でエラーになっています。getByText の引数には正規表現を利用することができるのでエスケープを行います。


const headerElement = screen.getByText(/Vite \+ React/);

再度テストを実行すると Invalid Chai property で toBeInTheDocument に関するエラーが発生します。


Error: Invalid Chai property: toBeInTheDocument

このエラーを解消するためには@testing-library/jest-dom のインストールが必要となります。toBeInTheDocument 関数は@testing-library/jest-dom の中に含まれているためです。インストールを行います。


 % npm install -D @testing-library/jest-dom

ここまでの設定で package.json ファイルは以下の通りです。


{
  "name": "vite-vitest-react",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.1.4",
    "@testing-library/react": "^14.1.0",
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@vitejs/plugin-react": "^4.0.3",
    "eslint": "^8.45.0",
    "eslint-plugin-react": "^7.32.2",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "jsdom": "^22.1.0",
    "vite": "^4.4.5",
    "vitest": "^0.34.6"
  }
}

ファイルの先頭で@testing-library/jest-domをimportします。


import '@testing-library/jest-dom/vitest';
import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import App from './App';

test('renders h1 text', () => {
  render(<App />);
  const headerElement = screen.getByText(/Vite \+ React/);
  expect(headerElement).toBeInTheDocument();
});

再度テストを実行するとようやくテストにパスします。


% npx vitest

 DEV  v0.34.6 /Users/mac/Desktop/vite-vitest-react

stderr | unknown test
The current test runner does not support afterEach/teardown hooks. This means we won't be able to run automatic cleanup and you should be calling cleanup() manually.
The current test runner does not support beforeAll/afterAll hooks. This means you should be setting IS_REACT_ACT_ENVIRONMENT manually.

 ✓ src/App.test.jsx (1)
   ✓ renders h1 text

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  21:02:51
   Duration  1.46s (transform 57ms, setup 0ms, collect 474ms, tests 35ms, environment 626ms, prepare 143ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

@testing-library/jest-dom の設定については setupTest.js ファイルを作成しその中に記述して vite.config.ts ファイルから setupFiles として指定することもできます。


import '@testing-library/jest-dom/vitest';

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    setupFiles: 'src/setupTest.js',
  },
});

さらにvite.config.tsファイルでglobalsを設定することでテストファイルでのtest, describe, expect関数のimport文を削除することができます。


import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: 'src/setupTests.js',
  },
});

import { render, screen } from '@testing-library/react';
import App from './App';

test('renders h1 text', () => {
  render(<App />);
  const headerElement = screen.getByText(/Vite \+ React/);
  expect(headerElement).toBeInTheDocument();
});

ReactでもVitestを利用してテストを行うことができました。

SvelteKit

プロジェクトの作成

Svelte のテストの動作確認は SvelteKit を利用して行います。SvelteKit のインストールは npm create svelte コマンドで行います。コマンドを実行すると unit testing に Vitest を利用するか聞かれるので”Yes”を選択します。


 % npm create svelte@latest vitest-sveltekit 

create-svelte version 2.3.3

Welcome to SvelteKit!

✔ Which Svelte app template? › Skeleton project
✔ Add type checking with TypeScript? › Yes, using JavaScript with JSDoc comments
✔ Add ESLint for code linting? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Playwright for browser testing? … No / Yes
✔ Add Vitest for unit testing? … No / Yes

Your project is ready!
✔ Type-checked JavaScript
  https://www.typescriptlang.org/tsconfig#checkJs
✔ Vitest
  https://vitest.dev

Install community-maintained integrations:
  https://github.com/svelte-add/svelte-adders

Next steps:
  1: cd vitest-sveltekit
  2: npm install (or pnpm install, etc)
  3: git init && git add -A && git commit -m "Initial commit" (optional)
  4: npm run dev -- --open

To close the dev server, hit Ctrl-C

Stuck? Visit us at https://svelte.dev/chat

プロジェクトの作成が完了するとプロジェクト名で指定したフォルダが作成されるのでフォルダに移動してnpm installコマンドを実行します。


 % cd vitest-sveltekit 
 % npm install

npm install完了後package.jsonファイルを確認するとvitestがインストールされていることが確認できます。scriptsにはtest:unitも登録されています。


{
  "name": "vitest-sveltekit",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "preview": "vite preview",
    "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
    "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
    "test:unit": "vitest"
  },
  "devDependencies": {
    "@sveltejs/adapter-auto": "^2.0.0",
    "@sveltejs/kit": "^1.5.0",
    "svelte": "^3.54.0",
    "svelte-check": "^3.0.1",
    "typescript": "^4.9.3",
    "vite": "^4.0.0",
    "vitest": "^0.25.3"
  },
  "type": "module"
}

テストの実行

src フォルダには index.test.js ファイルがデフォルトから作成されているので npm run testコマンドを実行してテストが実行できるか確認します。


 % npm run test:unit

> vitest-sveltekit@0.0.1 test:unit
> vitest


 DEV  v0.25.8 /Users/mac/Desktop/vitest-sveltekit

 ✓ src/index.test.js (1)

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  22:00:00
   Duration  1.01s (transform 409ms, setup 0ms, collect 16ms, tests 3ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

index.test.jsファイルの中身を確認してみると以下のシンプルなコードでアプリケーションを構成するコンポーネントを利用したものではありません。


import { describe, it, expect } from 'vitest';

describe('sum test', () => {
  it('adds 1 + 2 to equal 3', () => {
    expect(1 + 2).toBe(3);
  });
});

npm run devコマンドを実行すると開発サーバが起動してhttp://127.0.0.1:5173にアクセスすると初期ページが表示されます。

Skelton projectの初期画面
Skelton projectの初期画面

表示されている内容はroutes/+page.svelteファイルなので+page.svelteファイルを利用してテストを行います。

テストを行うためにsvelte-testing-libraryを利用します。


 % npm install --save-dev @testing-library/svelte

そのほかにも@testing-library/jest-dom、jsdomのインストールも行います。


 % npm install --save-dev @testing-library/jest-dom jsdom

インストール後のpackage.jsonファイルのdevDependenciesの内容は下記となります。


{
  "name": "vitest-sveltekit",
  "version": "0.0.1",
  "private": true,
  "scripts": {
//略
  },
  "devDependencies": {
    "@sveltejs/adapter-auto": "^2.0.0",
    "@sveltejs/kit": "^1.5.0",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/svelte": "^3.2.2",
    "jsdom": "^21.1.0",
    "svelte": "^3.54.0",
    "svelte-check": "^3.0.1",
    "typescript": "^4.9.3",
    "vite": "^4.0.0",
    "vitest": "^0.25.3"
  },
  "type": "module"
}

viteの設定ファイルであるvite.config.tsファイルのtestにenvironmemtとglobalsを設定します。


import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    include: ['src/**/*.{test,spec}.{js,ts}'],
    environment: 'jsdom',
    globals: true,
  },
});

設定が完了したのでsrcフォルダにtestフォルダを作成してその下にexample.test.jsファイルを作成して下記のテストコードを追加します。


import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/svelte';
import Home from '../routes/+page.svelte';

it('renders Welcome text', () => {
  render(Home);
  const Element = screen.getByText(/Welcome to SvelteKit/);
  expect(Element).toBeInTheDocument();
});

テストをnpm run test:unitコマンドで実行します。2つのテストにパスしていることが確認できます。


 % npm run test:unit

> vitest-sveltekit@0.0.1 test:unit
> vitest


 DEV  v0.25.8 /Users/mac/Desktop/vitest-sveltekit

 ✓ src/index.test.js (1)
 ✓ src/tests/example.test.js (1)

 Test Files  2 passed (2)
      Tests  2 passed (2)
   Start at  22:56:47
   Duration  2.12s (transform 591ms, setup 0ms, collect 537ms, tests 27ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

render関数によって描写したコンポーネントの内容を確認したい場合にはscreen.debugを利用することができます。


//略
it('renders Welcome text', () => {
  render(Home);
  const Element = screen.getByText(/Welcome to SvelteKit/);
  screen.debug();
  expect(Element).toBeInTheDocument();
});

npm run test:unitコマンドを実行したターミナルには+page.svelteのHTMLが表示されます。


stdout | src/tests/example.test.js > renders Welcome text
<body>
  <div>
    <h1>
      Welcome to SvelteKit
    </h1>
     
    <p>
      Visit 
      <a
        href="https://kit.svelte.dev"
      >
        kit.svelte.dev
      </a>
       to read the documentation
    </p>
  </div>
</body>

SvelteKitでのVitestの動作確認を行うことができました。