Remixを触ってみたいと思っていたら、React Router v7に統合されてしまいました。 そんなReact Routerでプロジェクトを作成し、PrismaとVitestを導入してテストを書き始める準備について書きました。
React Routerでプロジェクト作成
プロジェクトの作成は以下を実行します。
> npx create-react-router@latest my-react-router-app Need to install the following packages: create-react-router@7.0.2 Ok to proceed? (y) create-react-router v7.0.2 ◼ Directory: Using my-react-router-app as project directory ◼ Using default template See https://github.com/remix-run/react-router-templates for more ✔ Template copied git Initialize a new git repository? Yes deps Install dependencies with npm? No ◼ Skipping install step. Remember to install dependencies after setup with npm install. ✔ Git initialized done That's it! Enter your project directory using cd ./my-react-router-app Check out README.md for development and deploy instructions. Join the community at https://rmx.as/discord
docker composeでDBを用意
今回はPostgreSQLを使用します。
# compose.yaml services: db: image: postgres:16-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data: driver: local
Prisma導入
> pnpm i -D prisma > npx prisma init --datasource-provider postgresql
スキーマ定義
デフォルトではPrismaのスキーマで定義されたモデル名がそのままテーブル名となり作成されますが、
あまり馴染みがないので小文字の複数形で作成されるように@@map
でテーブル名を指定します。
// prisma/shema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { @@map("users") id Int @id @default(autoincrement()) email String @unique name String posts Post[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Post { @@map("posts") id Int @id @default(autoincrement()) title String @db.VarChar(255) content String @db.VarChar(255) published Boolean @default(false) user User @relation(fields: [userId], references: [id]) userId Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
開発用とテスト用のDBをそれぞれ作成
開発用とテスト用でDBを分けたいので、dotenvで読み込む環境変数を切り替える方法をとります。
> pnpm i -D dotenv-cli
.env.development、.env.testを作成し、それぞれに環境変数DATABASE_URL
を定義しておきます。
# .env.development # Environment variables declared in this file are automatically made available to Prisma. # See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema # Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. # See the documentation for all the connection string options: https://pris.ly/d/connection-strings DATABASE_URL="postgresql://postgres:password@localhost:5432/myapp_development"
# .env.test DATABASE_URL="postgresql://postgres:password@localhost:5432/myapp_test"
package.json
のscriptにDBへのスキーマ反映、マイグレーションコマンドを追加します。
dotenv-cli
により読み込む環境変数を指定して実行先を切り替えるようにしています。
追加内容
- スキーマ変更後、Prisma Clientの更新・型生成を行うための
npx prisma generate
コマンドを指定 - マイグレーション実行・ファイル生成を行う
npx prisma migrate dev
コマンドを指定- 初回DB作成を行う際は
init
オプションをつけて実行する - テスト環境DBに対しては本番環境と同様にマイグレーションファイル反映のみ行いたいので
npx prisma migrate deploy
コマンドを使用
- 初回DB作成を行う際は
// package.json { "name": "my-react-router-app", "private": true, "type": "module", "scripts": { "build": "react-router build", "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", "typecheck": "react-router typegen && tsc --build --noEmit", + "prisma": "npx prisma generate", + "db:migrate": "dotenv -e .env.development -- npx prisma migrate dev", + "db:migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy", }, ... }
Seeding
prismaフォルダにseed.tsファイルを追加し、seedを定義します。
import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function main() { const alice = await prisma.user.upsert({ where: { email: 'alice@prisma.io' }, update: {}, create: { email: 'alice@prisma.io', name: 'Alice', posts: { create: { title: 'Check out Prisma with Next.js', content: 'https://www.prisma.io/nextjs', published: true, }, }, }, }); const bob = await prisma.user.upsert({ where: { email: 'bob@prisma.io' }, update: {}, create: { email: 'bob@prisma.io', name: 'Bob', posts: { create: [ { title: 'Follow Prisma on Twitter', content: 'https://twitter.com/prisma', published: true, }, { title: 'Follow Nexus on Twitter', content: 'https://twitter.com/nexusgql', published: true, }, ], }, }, }); console.log({ alice, bob }); } main() .then(async () => { await prisma.$disconnect(); }) .catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });
.tsファイル実行のために別途tsx
をインストールしておきます。
(ドキュメントではts-node
を使用していますが、.tsファイルの拡張子がうまく認識されずエラーになる)
// package.json { "name": "my-react-router-app", "private": true, "type": "module", "scripts": { "build": "react-router build", "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", "typecheck": "react-router typegen && tsc --build --noEmit", "prisma": "npx prisma generate", "db:migrate": "dotenv -e .env.development -- npx prisma migrate dev", "db:migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy", + "db:seed": "dotenv -e .env.development -- npx prisma db seed", + "db:seed:test": "dotenv -e .env.test -- npx prisma db seed" }, + "prisma": { + "seed": "tsx prisma/seed.ts" + }, ... }
npm run db:seed
で実行するかmigrate実行時にseedが実行されます。
Vitest導入
Vitestと併せてjsdom
やtesting-library
各種をインストールします。
> pnpm i -D vitest jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom @testing-library/react-hooks
vite.config.tsの設定
React RouterではすでにViteが使用されているのでvite.config.ts
へ設定を行います。
@testing-library/jest-dom
のカスタムマッチャーを使用するために別途設定ファイルを作成してsetupFiles
へパスを指定します。
また、ReactRouterのViteプラグインはテストでの使用が想定されていないため(開発サーバーと本番ビルドでの使用のみの想定)、Vitestによるテスト実行時は使用されないように環境変数VITESTで制御します。 React Routerのドキュメントの方で見つけられなかったので、Remixのドキュメントを詳しくは参照してください。 remix.run
// vite.config.ts +/// <reference types="vitest/config" /> import { reactRouter } from '@react-router/dev/vite'; import autoprefixer from 'autoprefixer'; import tailwindcss from 'tailwindcss'; import { defineConfig } from 'vite'; import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ css: { postcss: { plugins: [tailwindcss, autoprefixer], }, }, + plugins: [!process.env.VITEST && reactRouter(), tsconfigPaths()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./tests/setup.ts'], + }, });
tsconfig.jsonの設定
JestのようなグローバルAPIとして使用するための設定もしておきます。 describe等のimportが省略できるようになって楽です。
// tsconfig.json
{
...
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "types": ["node", "vite/client", "vitest/globals"],
...
}
最後にpackage.jsonのscriptsへテストのコマンドを追加します。
// package.json
{
...
"scripts": {
...
+ "test": "vitest"
},
テストを書いてみる
最後にプロジェクト作成時に生成されるコンポーネントに対して簡単なテストを書いてみます。
react-routerが提供するcreateRoutesStub
を使用することでコンポーネントをルーティングに依存せず簡易的にモックしてテストが可能です。
import { createRoutesStub } from 'react-router'; import { render, screen } from '@testing-library/react'; import Home from '~/routes/home'; describe('React Router initial page', () => { it('renders the home page and navigation links', async () => { const Stub = createRoutesStub([{ path: '/', Component: Home }]); render(<Stub />); const logo = screen.getAllByRole('img', { name: /react router/i }).at(0); expect(logo).toBeInTheDocument(); await waitFor(() => { screen.findByText('React Router Docs'); screen.findByText('Join Discord'); }); }); });
さいごに
React Router、Prisma、Vitestによるテスト環境の構築を行いました。なにかしら参考になると嬉しいです。
ここまで読んでいただきありがとうございました。