コンテンツにスキップ
NextAuth.js v4 からの移行ですか? 移行ガイドをご覧ください.
ガイドテスト

テスト

認証の反復的で一貫したテストは、常に困難でした。特に OAuth プロバイダーは、新しい地理的な場所、データセンターの IP アドレス、新しいユーザーエージェントなどからログインしている場合にトリガーされる追加の検証手順を導入することが多いため、自動化された方法でテストするのが特に困難です。

これらの制限を回避するには、次のいずれかの戦略を使用して、Auth.js アプリケーションに対して E2E テストを実行することをお勧めします。

  1. Keycloak などのソフトウェアを使用して独自の OAuth プロバイダーを実行する
  2. 開発モードで Credentials プロバイダーなどの認証方法を有効にする

以下は、それぞれの戦略の例で、@playwright/test を自動化された E2E テストに活用しています。

Keycloak

まず、Keycloak インスタンスをセットアップします。次に、Keycloak プロバイダー を Auth.js 構成に追加する必要があります。

このテストでは、2 つの環境変数を設定する必要があります。これらの資格情報は、新しく作成した Keycloak インスタンスに対して認証できるテストユーザーのものである必要があります。

.env
TEST_KEYCLOAK_USERNAME=abc
TEST_KEYCLOAK_PASSWORD=123

次に、@playwright/test を使用して、2 つのテスト手順を実行できます。

  1. サインイン URL にアクセスし、認証資格情報を入力して、「サインイン」ボタンをクリックします。その後、セッションが正しく設定されていることを確認します。
  2. 「サインアウト」ボタンをクリックし、セッションが null になっていることを確認します。
tests/e2e/basic-auth.spec.ts
import { test, expect, type Page } from "@playwright/test"
 
test("Basic auth", async ({ page, browser }) => {
  if (
    !process.env.TEST_KEYCLOAK_USERNAME ||
    !process.env.TEST_KEYCLOAK_PASSWORD
  )
    throw new TypeError("Incorrect TEST_KEYCLOAK_{USERNAME,PASSWORD}")
 
  await test.step("should login", async () => {
    await page.goto("http://localhost:3000/auth/signin")
    await page.getByText("Keycloak").click()
    await page.getByText("Username or email").waitFor()
    await page
      .getByLabel("Username or email")
      .fill(process.env.TEST_KEYCLOAK_USERNAME)
    await page.locator("#password").fill(process.env.TEST_KEYCLOAK_PASSWORD)
    await page.getByRole("button", { name: "Sign In" }).click()
    await page.waitForURL("http://localhost:3000")
    const session = await page.locator("pre").textContent()
 
    expect(JSON.parse(session ?? "{}")).toEqual({
      user: {
        email: "bob@alice.com",
        name: "Bob Alice",
        image: "https://avatars.githubusercontent.com/u/67470890?s=200&v=4",
      },
      expires: expect.any(String),
    })
  })
 
  await test.step("should logout", async () => {
    await page.getByText("Sign out").click()
    await page
      .locator("header")
      .getByRole("button", { name: "Sign in", exact: true })
      .waitFor()
    await page.goto("http://localhost:3000/auth/session")
 
    expect(await page.locator("html").textContent()).toBe("null")
  })
})

Credentials プロバイダー(開発中)

この方法では、Keycloak などの個別の OAuth プロバイダーを維持する必要がないため、初期設定とメンテナンスが少なくて済みますが、本番環境で安全でない認証方法を使用しないように非常に注意する必要があります。たとえば、この例では、パスワード password を受け入れる Credentials プロバイダーを追加します。

auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Credentials from "next-auth/providers/credentials"
 
const providers = [GitHub]
 
if (process.env.NODE_ENV === "development") {
  providers.push(
    Credentials({
      id: "password",
      name: "Password",
      credentials: {
        password: { label: "Password", type: "password" },
      },
      authorize: (credentials) => {
        if (credentials.password === "password") {
          return {
            email: "bob@alice.com",
            name: "Bob Alice",
            image: "https://avatars.githubusercontent.com/u/67470890?s=200&v=4",
          }
        }
      },
    })
  )
}
 
export const { handlers, auth } = NextAuth({
  providers,
})

上記の構成例では、GitHub プロバイダーは常に追加され、Credentials プロバイダーは開発中にのみ追加されます。この構成の調整を行った後、上記のように @playwright/test テストを作成できます。

tests/e2e/basic-auth.spec.ts
import { test, expect, type Page } from "@playwright/test"
 
test("Basic auth", async ({ page, browser }) => {
  if (!process.env.TEST_PASSWORD) throw new TypeError("Missing TEST_PASSWORD")
 
  await test.step("should login", async () => {
    await page.goto("http://localhost:3000/auth/signin")
    await page.getByLabel("Password").fill(process.env.TEST_PASSWORD)
    await page.getByRole("button", { name: "Sign In" }).click()
    await page.waitForURL("http://localhost:3000")
    const session = await page.locator("pre").textContent()
 
    expect(JSON.parse(session ?? "{}")).toEqual({
      user: {
        email: "bob@alice.com",
        name: "Bob Alice",
        image: "https://avatars.githubusercontent.com/u/67470890?s=200&v=4",
      },
      expires: expect.any(String),
    })
  })
 
  await test.step("should logout", async () => {
    await page.getByText("Sign out").click()
    await page
      .locator("header")
      .getByRole("button", { name: "Sign in", exact: true })
      .waitFor()
    await page.goto("http://localhost:3000/auth/session")
 
    expect(await page.locator("html").textContent()).toBe("null")
  })
})
Auth.js © Balázs Orbán and Team -2024