测试工具

用于集成测试和端到端测试的测试实用工具

Test Utils 插件提供了针对 Better Auth 编写集成测试和端到端测试的辅助工具。它包括工厂函数、数据库辅助函数、认证辅助函数以及 OTP 捕获功能。

此插件仅适用于测试环境。请勿在生产环境中使用。

安装

在您的 auth 配置中添加该插件

auth.ts
import { betterAuth } from "better-auth"
import { testUtils } from "better-auth/plugins"

export const auth = betterAuth({
    // ... 其他配置选项
    plugins: [
        testUtils() 
    ]
})

通过上下文访问测试辅助工具

test-setup.ts
const ctx = await auth.$context
const test = ctx.test

使用方法

工厂函数

工厂函数创建对象但不写入数据库。用它们生成具有合理默认值的测试数据。

createUser

创建一个带有默认值(可被覆盖)的用户对象。

// 使用默认值创建用户
const user = test.createUser()
// { id: "...", email: "user-xxx@example.com", name: "Test User", emailVerified: true, ... }

// 使用自定义值创建用户
const user = test.createUser({
    email: "alice@example.com",
    name: "Alice",
    emailVerified: false
})

createOrganization

创建一个组织对象。仅在安装了组织插件时可用。

const org = test.createOrganization({
    name: "Acme Corp",
    slug: "acme-corp"
})

数据库辅助函数

数据库辅助函数可将测试数据保存到数据库或从数据库中删除数据。

saveUser

将用户保存到数据库。

const user = test.createUser({ email: "test@example.com" })
const savedUser = await test.saveUser(user)

deleteUser

从数据库中删除用户。

await test.deleteUser(user.id)

saveOrganization

将组织保存到数据库。仅在安装了组织插件时可用。

const org = test.createOrganization({ name: "Test Org" })
const savedOrg = await test.saveOrganization(org)

deleteOrganization

从数据库中删除组织。仅在安装了组织插件时可用。

await test.deleteOrganization(org.id)

addMember

将用户添加为组织成员。仅在安装了组织插件时可用。

const member = await test.addMember({
    userId: user.id,
    organizationId: org.id,
    role: "admin"
})

认证辅助函数

认证辅助函数用于创建认证会话,以测试受保护路由。

login

为用户创建会话并返回会话详情、请求头、cookies 和令牌。

const { session, user, headers, cookies, token } = await test.login({
    userId: user.id
})

// session - 包含 userId、token 等信息的会话对象
// user - 用户对象
// headers - 带有会话 cookie 的 Headers 对象(用于 fetch/Request)
// cookies - Cookie 数组(用于 Playwright/Puppeteer)
// token - 会话令牌字符串

getAuthHeaders

返回带有会话 cookie 的 Headers 对象,适合进行认证请求。

const headers = await test.getAuthHeaders({ userId: user.id })

// 与 auth API 一起使用
const session = await auth.api.getSession({ headers })

// 与 fetch 一起使用
const response = await fetch("/api/protected", { headers })

getCookies

返回符合浏览器测试工具(如 Playwright 和 Puppeteer)要求的 cookie 对象数组。

const cookies = await test.getCookies({
    userId: user.id,
    domain: "localhost" // 可选,默认使用 baseURL 域名
})

// Playwright 示例
await context.addCookies(cookies)

// Puppeteer 示例
for (const cookie of cookies) {
    await page.setCookie(cookie)
}

每个 cookie 对象包含:

  • name - Cookie 名称(例如 "better-auth.session_token")
  • value - Cookie 值
  • domain - Cookie 域名
  • path - Cookie 路径(默认为 "/")
  • httpOnly - 是否为 HttpOnly cookie
  • secure - 是否需要 HTTPS
  • sameSite - SameSite 属性("Lax"、"Strict" 或 "None")

OTP 捕获

当设置 captureOTP: true 时,插件会被动捕获创建的 OTP。这让你可以在测试中获取 OTP,而无需模拟邮件或短信发送。

OTP 捕获为被动过程——不会阻止 OTP 通过你配置的 sendVerificationOTP 函数发送。它仅存储一份用于测试获取的副本。

auth.ts
import { betterAuth } from "better-auth"
import { testUtils, emailOTP } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        testUtils({ captureOTP: true }), 
        emailOTP({
            async sendVerificationOTP({ email, otp }) {
                // 你的邮件发送逻辑
            }
        })
    ]
})

getOTP

通过标识符(邮箱或手机号)获取捕获的 OTP。

// 发送 OTP
await auth.api.sendVerificationOTP({
    body: { email: "user@example.com", type: "sign-in" }
})

// 获取捕获的 OTP
const otp = test.getOTP("user@example.com")
// "123456"

选项

选项类型默认值说明
captureOTPbooleanfalse是否启用 OTP 捕获,用于测试验证流程

示例

集成测试(Vitest)

import { describe, it, expect, beforeAll } from "vitest"
import { auth } from "./auth"
import type { TestHelpers } from "better-auth/plugins"

describe("protected route", () => {
    let test: TestHelpers

    beforeAll(async () => {
        const ctx = await auth.$context
        test = ctx.test
    })

    it("应该为认证请求返回用户数据", async () => {
        // 准备数据
        const user = test.createUser({ email: "test@example.com" })
        await test.saveUser(user)

        // 获取认证请求头
        const headers = await test.getAuthHeaders({ userId: user.id })

        // 测试认证请求
        const session = await auth.api.getSession({ headers })
        expect(session?.user.id).toBe(user.id)

        // 清理
        await test.deleteUser(user.id)
    })
})

端到端测试(Playwright)

import { test, expect } from "@playwright/test"
import { auth } from "./auth"

test("仪表盘显示用户名", async ({ context, page }) => {
    const ctx = await auth.$context
    const testUtils = ctx.test

    // 创建并保存用户
    const user = testUtils.createUser({
        email: "e2e@example.com",
        name: "E2E User"
    })
    await testUtils.saveUser(user)

    // 获取 cookies 并注入浏览器
    const cookies = await testUtils.getCookies({
        userId: user.id,
        domain: "localhost"
    })
    await context.addCookies(cookies)

    // 导航至受保护页面
    await page.goto("/dashboard")

    // 断言用户名可见
    await expect(page.getByText("E2E User")).toBeVisible()

    // 清理
    await testUtils.deleteUser(user.id)
})

OTP 验证测试

import { describe, it, expect, beforeAll, beforeEach } from "vitest"
import { auth } from "./auth"
import type { TestHelpers } from "better-auth/plugins"

describe("OTP 验证", () => {
    let test: TestHelpers

    beforeAll(async () => {
        const ctx = await auth.$context
        test = ctx.test
    })

    beforeEach(() => {
        test.clearOTPs()
    })

    it("应该使用捕获的 OTP 验证邮箱", async () => {
        const email = "otp-test@example.com"
        const user = test.createUser({ email, emailVerified: false })
        await test.saveUser(user)

        // 请求 OTP
        await auth.api.sendVerificationOTP({
            body: { email, type: "email-verification" }
        })

        // 获取捕获的 OTP
        const otp = test.getOTP(email)
        expect(otp).toBeDefined()

        // 验证邮箱
        await auth.api.verifyEmail({
            body: { email, otp }
        })

        // 清理
        await test.deleteUser(user.id)
    })
})