跳至内容
从 NextAuth.js v4 迁移?阅读 我们的迁移指南.
指南测试

测试

对身份验证进行重复且一致的测试一直很棘手。特别是 OAuth 提供商,在自动化方式中很难测试,因为它们通常会引入额外的验证步骤,这些步骤会在您从新的地理位置、数据中心 IP 地址或新的用户代理等登录时触发。

为了克服这些限制,我们建议您使用以下策略之一来针对 Auth.js 应用程序运行成功的 E2E 测试。

  1. 使用 Keycloak 等软件运行您自己的 OAuth 提供商Keycloak
  2. 在开发模式下启用身份验证方法,如 Credentials 提供商

以下是每种策略的一个示例,利用 @playwright/test 用于自动化的 E2E 测试。

Keycloak

首先,设置一个 Keycloak 实例。然后您必须将 Keycloak 提供商 添加到您的 Auth.js 配置中。

此测试需要设置两个环境变量。这些凭据应该是用于可以针对您新创建的 Keycloak 实例进行身份验证的测试用户。

.env
TEST_KEYCLOAK_USERNAME=abc
TEST_KEYCLOAK_PASSWORD=123

然后我们可以使用 @playwright/test 来执行两个测试步骤。

  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("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: "[email protected]",
        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),但您也必须非常小心,不要在生产环境中使用不安全的身份验证方法。例如,在本例中,我们将添加一个 Credentials 提供商,它接受密码 password

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: "[email protected]",
            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: "[email protected]",
        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