🧪
Nuxt3 × 単体テスト × E2Eテスト実装例
はじめに
この記事は、単体テストやE2Eテストを自動化させるにあたって
整理した要点や実装例を記載したものである。
この記事の対象は、テスト自動化の初心者に向けたものである。
環境(一部抜粋)
- Nuxt3
- Vitest
- Playwright
単体テストの要点
- テスト名は日本語で開発者以外が理解できるものにすること
- テスト名は3つのレベルを含めること
sample.test.ts
describe('ドメイン', () => {
describe('ユースケースやシナリオ', () => {
test('期待する結果', () => {
})
})
})
- テストケースは、AAAパターンに従うこと
sample.test.ts
it('期待する結果', () => {
// 準備 Arrange
// 実行 Act
// 確認 Assert
})
- 内部実装の詳細をテストしないこと
- テスト対象は、各ドメインモデルとすること
また、一部複雑なロジックのクラスや関数は例外的に単体テストを行うこと
コントローラーやレポジトリの単体テストはせず、E2Eテストで間接的にテストを行うこと
Vitestによる単体テストの実装例
npm run test
でテストを実行する想定
package.json
{
"scripts": {
"test": "vitest"
}
}
vitest.config.ts
import path from "path";
import { defineConfig, configDefaults } from "vitest/config";
export default defineConfig({
test: {
// "e2e/*"はE2Eテスト用のフォルダとして使用するためテスト対象から除外する
exclude: [...configDefaults.exclude, "e2e/*"],
// テストファイルのインポート文でパス解決が必要な場合は、ここを修正する
alias: {
"~~": path.resolve(__dirname),
},
},
});
tests/useEntity.test.ts
import { describe, test, expect } from "vitest";
import { UserEntity } from "../server/domain/user/userEntity";
// ユーザードメインの機能要件をテストする
describe("ユーザー", () => {
describe("ユーザーを登録する", () => {
test("ユーザー登録できること", () => {
// 準備
const testData = {
uid: "random-uid",
name: "田中太郎",
mailAddress: "sample@gmail.com",
};
// 実行
const user = UserEntity.create(testData);
// 確認
expect(user.data?.value).toEqual({ ...testData, status: "active" });
});
test("ユーザー登録する場合、uidは必須であること", () => {
// 準備
const testData = {
uid: "",
name: "田中太郎",
mailAddress: "sample@gmail.com",
};
// 実行
const user = UserEntity.create(testData);
// 確認
expect(user.error?.message).toBe("UIDは必須です。");
});
test("ユーザー登録する場合、ユーザー名は必須であること", () => {
// 準備
const testData = {
uid: "random-id",
name: "",
mailAddress: "sample@gmail.com",
};
// 実行
const user = UserEntity.create(testData);
// 確認
expect(user.error?.message).toBe("ユーザー名は必須です。");
});
test("ユーザー登録する場合、メールアドレスは必須であること", () => {
// 準備
const testData = {
uid: "random-id",
name: "田中太郎",
mailAddress: "",
};
// 実行
const user = UserEntity.create(testData);
// 確認
expect(user.error?.message).toBe("メールアドレスは必須です。");
});
});
});
E2Eテストの要点
- テスト名は日本語で開発者以外が理解できるものにする
- テスト名は3つのレベルを含めること
sample.test.ts
// 例
test.describe("画面", async () => {
test.describe("ふるまい", async () => {
test("期待する結果", async ({ page }) => {
});
});
});
- Page Object Modelsパターンに従うこと
- 原則的に1テストケースあたり複数確認フェーズOKとする
- 原則的にロケーターは文言から取得するが、例外的にdata-testidも許容する
PlaywrightによるE2Eテストの実装例
npm run e2e
でE2Eテストを実行する想定
package.json
{
"scripts": {
"dev": "dotenv -e .env.development -- nuxi dev",
"e2e": "npx playwright test"
}
}
playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
// テストフォルダ格納先パス指定
testDir: "./e2e",
timeout: 30 * 1000,
expect: {
timeout: 5000,
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: "html",
use: {
actionTimeout: 0,
baseURL: "http://localhost:3000",
trace: "on-first-retry",
},
// Chromeブラウザのみでテストを確認する
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
// {
// name: "firefox",
// use: { ...devices["Desktop Firefox"] },
// },
// {
// name: "webkit",
// use: { ...devices["Desktop Safari"] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { channel: 'chrome' },
// },
],
// outputDir: 'test-results/',
// テスト実行時、Nuxt3開発サーバーを事前に起動する
webServer: {
command: "npm run dev",
port: 3000,
},
});
e2e/lndexPage.test.ts
import { test, expect } from "@playwright/test";
test.describe("ログイン画面", async () => {
test("初期表示", async ({ page }) => {
await page.goto("/");
await page.getByText("App").waitFor();
await expect(page).toHaveTitle(/ログイン/);
await expect(page.getByRole("heading", { name: "App" })).toBeVisible();
await expect(page.getByLabel("メールアドレス")).toBeVisible();
await expect(page.getByLabel("パスワード")).toBeVisible();
await expect(page.getByRole("button", { name: "ログイン" })).toBeVisible();
await expect(
page.getByRole("link", { name: "または、新規登録はこちら" })
).toBeVisible();
});
});
参考
Discussion