測試
重複且一致地測試身份驗證一直很棘手。尤其是 OAuth 提供者,在自動化的方式下特別難以測試,因為它們通常會引入額外的驗證步驟,如果您從新的地理位置、數據中心 IP 位址或新的使用者代理程式等登入,將會觸發這些步驟。
為了克服這些限制,我們建議您使用以下策略之一,針對 Auth.js 應用程式執行成功的 E2E 測試。
- 使用像 Keycloak 之類的軟體來執行您自己的 OAuth 提供者
- 在開發模式中啟用像 Credentials 提供者這樣的身份驗證方法
以下是每個策略的範例,利用 @playwright/test 進行自動化的 E2E 測試。
Keycloak
首先,設定一個 Keycloak 執行個體。然後您必須將 Keycloak 提供者新增至您的 Auth.js 設定。
此測試需要設定兩個環境變數。這些憑證應適用於可以針對您新建立的 Keycloak 執行個體進行驗證的測試使用者。
.env
TEST_KEYCLOAK_USERNAME=abc
TEST_KEYCLOAK_PASSWORD=123
然後我們可以使用 @playwright/test
來執行兩個測試步驟。
- 這將會訪問登入 URL,輸入身份驗證憑證,然後按一下「登入」按鈕。它會確保 session 接著被正確設定。
- 這將會按一下「登出」按鈕,並確保 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")
})
})