跳至內容
從 NextAuth.js v4 遷移?請閱讀 我們的遷移指南.
指南測試

測試

重複且一致地測試身份驗證一直很棘手。尤其是 OAuth 提供者,在自動化的方式下特別難以測試,因為它們通常會引入額外的驗證步驟,如果您從新的地理位置、數據中心 IP 位址或新的使用者代理程式等登入,將會觸發這些步驟。

為了克服這些限制,我們建議您使用以下策略之一,針對 Auth.js 應用程式執行成功的 E2E 測試。

  1. 使用像 Keycloak 之類的軟體來執行您自己的 OAuth 提供者
  2. 在開發模式中啟用像 Credentials 提供者這樣的身份驗證方法

以下是每個策略的範例,利用 @playwright/test 進行自動化的 E2E 測試。

Keycloak

首先,設定一個 Keycloak 執行個體。然後您必須將 Keycloak 提供者新增至您的 Auth.js 設定。

此測試需要設定兩個環境變數。這些憑證應適用於可以針對您新建立的 Keycloak 執行個體進行驗證的測試使用者。

.env
TEST_KEYCLOAK_USERNAME=abc
TEST_KEYCLOAK_PASSWORD=123

然後我們可以使用 @playwright/test 來執行兩個測試步驟。

  1. 這將會訪問登入 URL,輸入身份驗證憑證,然後按一下「登入」按鈕。它會確保 session 接著被正確設定。
  2. 這將會按一下「登出」按鈕,並確保 session 接著為 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("https://127.0.0.1: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("https://127.0.0.1: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("https://127.0.0.1:3000/auth/session")
 
    expect(await page.locator("html").textContent()).toBe("null")
  })
})

開發中的 Credentials 提供者

此方法需要的初始設定和維護較少,因為您不需要維護單獨的 OAuth 提供者(如 Keycloak),但您也必須非常小心,不要在生產環境中保留不安全的身份驗證方法。例如,在此範例中,我們將新增一個接受密碼 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("https://127.0.0.1:3000/auth/signin")
    await page.getByLabel("Password").fill(process.env.TEST_PASSWORD)
    await page.getByRole("button", { name: "Sign In" }).click()
    await page.waitForURL("https://127.0.0.1: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("https://127.0.0.1:3000/auth/session")
 
    expect(await page.locator("html").textContent()).toBe("null")
  })
})
Auth.js © Balázs Orbán 和團隊 -2024