1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js(App Router)で作ったTODOアプリにVitestでテストを追加しました

Posted at

背景

前回の記事で、Next.js(App Router)、Zod、Prisma、React Hook Formを使用してTODOリストアプリケーションを作成しました。

今回は、このアプリケーションにvitestを使用してテストを追加する方法を紹介します。

GitHubリポジトリはこちらです。

Vitestとは

vitestは、Viteベースの高速なJavaScriptテストフレームワークです。Jest互換のAPIを持ち、TypeScriptのサポートも優れています。

1. 必要なパッケージのインストール

yarn add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/jest-dom
yarn add testcontainers @testcontainers/postgresql

2. 設定ファイル

vite.config.tsの作成

vite.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import path from "path";

export default defineConfig({
  plugins: [react()],
  test: {
    environment: "jsdom",
    globals: true,
    setupFiles: ["./vitest.setup.ts"],
    include: ["src/**/*.test.ts"],
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});

tsconfig.jsonの更新

tsconfig.json
{
  "compilerOptions": {
    "types": ["node", "vitest/globals", "testcontainers"]
  },
  "include": [
    "tests/**/*.ts"
  ]
}

3. テスト用データベースのセットアップ

テスト用のデータベースをセットアップするために、testcontainersを使用します。これにより、テストごとに隔離された環境を簡単に作成できます。また、setTestPrisma関数を使用して、マイグレーション済みのPrismaインスタンスを維持します。

testcontainersを使ったデータベースセットアップ

vitest.setup.tsファイルを作成し、以下のコードを追加します:

vitest.setup.ts
import "@testing-library/jest-dom";
import { beforeAll, afterAll } from "vitest";
import { PrismaClient } from "@prisma/client";
import { execSync } from "child_process";
import {
  PostgreSqlContainer,
  StartedPostgreSqlContainer,
} from "@testcontainers/postgresql";
import { setTestPrisma } from "./prisma";

let container: StartedPostgreSqlContainer;
let prisma: PrismaClient;

export async function setupTestDBContainer() {
  // testcontainersを使用してPostgreSQLコンテナを起動
  container = await new PostgreSqlContainer("public.ecr.aws/docker/library/postgres:16.1-alpine")
    .withDatabase("todo_db")
    .withUsername("todouser")
    .withPassword("todopassword")
    .withReuse()
    .start();

  const port = container.getMappedPort(5432);
  
  // データベースURLを環境変数に設定
  process.env["DATABASE_URL"] = `postgresql://${container.getUsername()}:${container.getPassword()}@${container.getHost()}:${port}/todo_db?schema=public`;

  // Prismaマイグレーションを実行
  execSync("npx prisma migrate deploy", { stdio: "inherit" });

  // 新しいPrismaインスタンスを作成
  prisma = new PrismaClient({
    datasources: {
      db: {
        url: process.env.DATABASE_URL,
      },
    },
  });

  // マイグレーション済みのPrismaインスタンスをセット
  setTestPrisma(prisma);
}

beforeAll(async () => {
  console.log("Start Global setup");
  await setupTestDBContainer();
  console.log("End Global setup");
});

afterAll(async () => {
  console.log("Start Global teardown");
  await prisma.$disconnect();
  await container.stop();
  console.log("End Global teardown");
});

setTestPrismaの実装

prisma.tsファイルに以下のコードを追加して、setTestPrisma関数を実装します:

prisma.ts
import { PrismaClient } from "@prisma/client";

let prisma: PrismaClient;

export function getPrismaClient(): PrismaClient {
  if (!prisma) {
    prisma = new PrismaClient();
  }
  return prisma;
}

export function setTestPrisma(newPrisma: PrismaClient) {
  prisma = newPrisma;
}

なぜこのアプローチが重要か

1. 隔離された環境

testcontainersを使用することで、各テスト実行時に独立したデータベース環境を確保できます。これにより、テスト間の干渉を防ぎ、より信頼性の高いテスト結果を得ることができます。

2. 再現性

コンテナ化されたデータベースを使用することで、ローカル環境やCI/CD環境で一貫したテスト環境を維持できます。

3. マイグレーション済みPrismaインスタンスの維持

setTestPrisma関数を使用することで、マイグレーション後のPrismaインスタンスを全てのテストで共有できます。これにより、各テストでデータベーススキーマが正確に反映されていることを保証できます。

4. パフォーマンス

テスト実行ごとにデータベースをセットアップするのではなく、マイグレーション済みのインスタンスを再利用することで、テストの実行速度を向上させることができます。

このアプローチを採用することで、より堅牢で信頼性の高いテスト環境を構築でき、アプリケーションの品質向上に貢献します。

4. テストの実装

src/app/action.medium.test.tsファイルを作成し、TODOアクションのテストを実装します。

src/app/action.medium.test.ts
import { describe, expect, test } from "vitest";
import { addTodo, deleteTodo, toggleTodo } from "./actions";

describe("addTodo", () => {
  test("addTodoが正常に動作すること", async () => {
    const newTodo = { title: "New todo" };
    await expect(addTodo(newTodo)).resolves.toEqual({
      id: 1,
      title: "New todo",
    });
  });

  test("空のタイトルでは、addTodoが失敗すること", async () => {
    const newTodo = { title: "" };
    await expect(addTodo(newTodo)).rejects.toThrow();
  });
});

describe("toggleTodo", () => {
  test("toggleTodoが正常に動作すること", async () => {
    const newTodo = { title: "New todo" };
    const { id } = await addTodo(newTodo);
    await expect(deleteTodo(id)).resolves.toBeUndefined();
  });

  test("存在しないidでは、deleteTodoが失敗すること", async () => {
    await expect(deleteTodo(100)).rejects.toThrow();
  });
});

5. テストの実行

テストを実行するには、以下のコマンドを使用します:

yarn vitest

まとめ

この記事では、Next.js(App Router)で作成したTODOアプリにvitestを使用してテストを追加する方法を紹介しました。主な学びポイントは以下の通りです:

  • vitestの基本的な設定方法
  • テスト用データベースのセットアップ
  • サーバーアクションのテスト実装

このアプローチを採用することで、アプリケーションの信頼性を向上させ、バグの早期発見に役立ちます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?