はじめに
こんにちは。アノテーションの及川です。
業務の中で、Vite + Vitest のコードを確認する機会があり、その構成や使い方を学ぶためハンズオン形式で実施したことを整理しました。
Vite
Vite の詳細はこちらからご参照ください。
概要
- Vite(フランス語で「速い」を意味する)は、高速な開発サーバーとビルドツールを提供します。
- Vite は従来のモジュールバンドラーとは異なり、開発時にはネイティブESモジュールを利用してブラウザで直接モジュールをインポートします。これにより、開発時の起動が非常に高速になります。
特徴
- 高速なコールドスタート
- Vite は開発環境でのページリロードを必要とせず、依存関係のプリバンドリングによってプロジェクトの起動を高速化します。
- ホットモジュールリプレースメント (HMR)
- ファイルが変更されたときに、その変更を即座にブラウザに反映させることができ、開発プロセスをスムーズにします。
- ビルド最適化
- Vite は開発中はモジュールバンドラーを使用しませんが、本番環境用のビルドでは Rollup を使用して最適化されたアセットを生成します。これにより、本番環境でのパフォーマンスが向上します。
Vitest 概要
Vitest の詳細はこちらからご参照ください。
概要
- Vitest は、Vite エコシステムの一部として開発されたモダンなテストランナーおよびアサーションライブラリです。
- Vite の高速なモジュール解決とトランスパイル機能を利用して、テストの実行を高速化します。
- Jest に似たAPIを提供するため、Jestからの移行も比較的簡単です。
特徴
- 高速なテスト実行
- Vite のインフラストラクチャを利用して、テストの起動と実行を高速化します。
- Jest 互換
- Jest と同様の API を提供し、既存の Jest テストを Vitest に容易に移行できるように設計されています。
- ネイティブ ESM サポート
- ネイティブ ES モジュールをフルサポートしており、トランスパイルを必要とせずにテストを実行できます。
Vite インストール
「React」 + 「TypeScript + SWC」 の構成でインストールします。
SWC は、次世代の高速開発ツールのための拡張可能な Rust ベースのプラットフォームであり、コンパイルとバンドルの両方に使用できます。
最新の JavaScript 機能を使用して JavaScript / TypeScript ファイルを受け取り、すべての主要なブラウザーでサポートされる有効なコードを出力します。
% node --version
v20.11.0
% npm create vite@latest vitest-setup-test
Need to install the following packages:
create-vite@5.2.3
Ok to proceed? (y) # ← 'y'で良いのでそのまま Enter
? Select a framework: › - Use arrow-keys. Return to submit.
Vanilla
Vue
❯ React
Preact
Lit
Svelte
Solid
Qwik
Others
✔ Select a framework: › React
? Select a variant: › - Use arrow-keys. Return to submit.
TypeScript
❯ TypeScript + SWC
JavaScript
JavaScript + SWC
Remix ↗
✔ Select a framework: › React
✔ Select a variant: › TypeScript + SWC
Scaffolding project in /Users/anote.taro/dev/Introduction-to-React-Testing-with-Vitest/vitest-setup-test...
Done. Now run:
cd vitest-setup-test
npm install
npm run dev
% cd vitest-setup-test
% npm install
added 163 packages, and audited 164 packages in 9s
38 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
% code . # (任意)Visual Studio Code エディタで開く場合は下記コマンド入力して Enter
【任意】インポート(import) パスエイリアス設定
下記の例のように、別のディレクトリ配下のファイルを import する際に、"相対パス"で指定するところを、"@/~"の形で指定することができます。
階層が深いディレクリ構成になってくると、../../../../../~
のように指定するとコードを追うのが大変になったり、パス指定を誤ったりするため、"@" マークを基点に指定することが可能になります。
Vitest の環境構築の過程でついでに設定できるので、よろしければ是非設定をお試しください。
import Book from "./components/Book.tsx" // 相対パス
↓
import Book from "@/components/Book.tsx" // @マークを基点としたパス指定
下記部分を tsconfig.json
に追記してください。
下記はプロジェクトルートを基点として、src ディレクトリ配下の全てのディレクトリは @/~
でインポートできるという意味です。
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
Vitest に必要なライブラリをインストールして設定する
下記コマンドで設定に必要なライブラリをインストールします。
% npm install -D vitest happy-dom @vitest/coverage-v8 @testing-library/react @testing-library/user-event @testing-library/
jest-dom
今回はテストやブラウザ環境のエミュレートで使用でき、JSDOM よりも処理が早いとされている Happy DOM をインストールして使ってみます。
Vitest に必要な設定
他に Vitest を実行するための下記設定を tsconfig.json 記述していきます。
"types": ["vitest/globals"]
},
"include": [
"src",
"node_modules/vitest/global.d.ts",
"vite.config.ts",
"vitest-setup.ts"
],
packgae.json の scripts を編集
npm run test
とコマンドを実行すると vitest
が実行されるように "test": "vitest"
と追記します。
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest"
},
vite.config.ts の設定
vite でプロジェクトを作成しているため、vite.config.ts
ファイルも編集する必要があります。
vite.config.ts
ファイルの編集を簡単にするため、vite-tsconfig-paths
をインストールして、vite.config.ts
ファイルの plugins に設定します。
設定することにより tsconfig.json の変更だけで、vite.config.ts
ファイルの設定内容も自動的に反映することができます。
% npm install -D vite-tsconfig-paths
続けて、Vitest に必要な下記設定を記載します。
/// <reference types="vitest"/>
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
environment: "happy-dom",
setupFiles: ["./vitest-setup.ts"],
},
});
上記、setupFiles に記載した vitest-setup.ts
はプロジェクト配下に作成します。
% touch vitest-setup.ts
続けて、下記設定を記載します。
import "@testing-library/jest-dom/vitest";
ここまでの設定内容
下記は、設定の参考情報(一例)としてご参照ください。
package.json
{
"name": "vitest-setup-test",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.2",
"@testing-library/user-event": "^14.5.2",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-v8": "^1.4.0",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"happy-dom": "^14.3.9",
"typescript": "^5.2.2",
"vite": "^5.2.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.4.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"types": ["vitest/globals"]
},
"include": [
"src",
"node_modules/vitest/global.d.ts",
"vite.config.ts",
"vitest-setup.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
}
vite.config.ts
/// <reference types="vitest"/>
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
environment: "happy-dom",
setupFiles: ["./vitest-setup.ts"],
},
});
vitest-setup.ts
import "@testing-library/jest-dom/vitest";
Vitest でテストコードを書いてみる
src/components/Input/TextInput.tsx
import React, { ChangeEvent, useState } from "react";
const TextInput = () => {
const = useState<string>("");
return (
<div>
<input
type="text"
onChange={(e: ChangeEvent<HTMLInputElement>) => setText(e.target.value)}
value={text}
placeholder="Enter some text"
/>
<p>{text}</p>
</div>
);
};
export default TextInput;
src/components/Input/TextInput.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import TextInput from "./TextInput";
test("TextInput Component test", () => {
render(<TextInput />);
const inputElement = screen.getByRole("textbox");
expect(inputElement).toBeInTheDocument();
});
test("TextInput Event Test", async () => {
const user = userEvent.setup();
render(<TextInput />);
const inputElement = screen.getByRole("textbox");
await user.type(inputElement, "Vitest Test!");
expect(screen.getByText("Vitest Test!")).toBeInTheDocument();
});
テスト実行
% npm run test
> vitest-setup-test@0.0.0 test
> vitest
DEV v1.4.0 /Users/anote.taro/dev/Introduction-to-React-Testing-with-Vitest/vitest-setup-test
✓ src/components/Input/TextInput.test.tsx (2)
✓ TextInput Component test
✓ TextInput Event Test
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 23:40:16
Duration 535ms (transform 114ms, setup 112ms, collect 166ms, tests 47ms, environment 93ms, prepare 47ms)
PASS Waiting for file changes...
press h to show help, press q to quit
まとめ
Vite + Vitest の導入からテストまでのハンズオンの過程を記載しました。
この記事が誰かのお役に立てば幸いです。
アノテーション株式会社について
アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。