테스트
인증 기능을 반복적이고 일관성 있게 테스트하는 것은 항상 까다로운 작업이었습니다. 특히 OAuth 프로바이더는 자동화된 방식으로 테스트하기가 더 어려운데, 새로운 지리적 위치, 데이터센터 IP 주소, 새로운 사용자 에이전트 등에서 로그인할 경우 추가적인 검증 단계가 발생하기 때문입니다.
이러한 제약을 극복하기 위해 Auth.js 애플리케이션에 대한 성공적인 E2E 테스트를 실행할 수 있는 전략을 소개합니다.
- Keycloak과 같은 소프트웨어를 사용해 직접 OAuth 프로바이더를 실행
- 개발 모드에서 Credentials 프로바이더와 같은 인증 방법 활성화
아래는 각 전략의 예시로, 자동화된 E2E 테스트를 위해 @playwright/test를 활용한 방법입니다.
Keycloak
먼저 Keycloak 인스턴스를 설정합니다. 그런 다음 Auth.js 설정에 Keycloak 프로바이더를 추가해야 합니다.
이 테스트를 실행하려면 두 개의 환경 변수를 설정해야 합니다. 이 자격 증명은 새로 생성한 Keycloak 인스턴스에 대해 인증할 수 있는 테스트 사용자의 정보여야 합니다.
TEST_KEYCLOAK_USERNAME=abc
TEST_KEYCLOAK_PASSWORD=123
그런 다음 @playwright/test
를 사용하여 두 가지 테스트 단계를 실행할 수 있습니다.
- 로그인 URL을 방문하고, 인증 자격 증명을 입력한 다음 “Sign In” 버튼을 클릭합니다. 이 단계는 세션이 올바르게 설정되었는지 확인합니다.
- “Sign Out” 버튼을 클릭하고 세션이
null
로 설정되었는지 확인합니다.
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
프로바이더
이 방법은 별도의 OAuth 프로바이더(예: Keycloak)를 유지할 필요가 없어 초기 설정과 유지보수가 간단하지만, 프로덕션 환경에서 안전하지 않은 인증 방법을 남겨두지 않도록 각별히 주의해야 합니다. 예를 들어, 이 예제에서는 password
라는 비밀번호를 허용하는 Credentials 프로바이더를 추가할 것입니다.
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
테스트를 작성할 수 있습니다.
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")
})
})