Docs

Testing in Better Auth

Overview

We use Vitest as our testing framework of choice. Currently, we focus on unit tests, with plans to expand to integration testing in the future. This guide will help you understand our testing philosophy and how to write effective tests.

We expect you to have knowledge of Vitest before you start writing tests. If you're new to Vitest, we recommend you read the Vitest documentation first.

Testing Philosophy

Our approach to testing emphasizes:

  • Simplicity over complexity
  • Real implementations over mocks
  • Co-location of tests with source code
  • Minimal test setup
  • Clear and readable test cases

Getting Started

Setup

# Install dependencies including Vitest
pnpm install
 
# Run tests
pnpm run test

Test Structure

File Organization

We follow a co-location strategy for tests. Place your test files next to the source files they test:

auth.ts
auth.test.ts
oauth.ts
oauth.test.ts

Test File Naming

  • Use .test.ts extension for test files
  • Name test files after the module they test
  • For TypeScript, use .test.ts or .test.tsx for React components

Writing Tests

Basic Test Structure

This is just example code, and isn't meant to be used as-is. It's meant to demonstrate the structure of a test file.

auth.test.ts
//...
import { describe, it, expect } from "vitest";
import { getTestInstance } from "./test-utils/test-instance";
 
describe("Authentication Module", () => {
  it("should successfully authenticate a user", async () => {
    const { client } = getTestInstance();
    const result = await client.signIn.email({
      email: "[email protected]",
      password: "123456",
    });
 
    expect(result.success).toBe(true);
    expect(result.user).toBeDefined();
  });
});

Using getTestInstance

The test instance provides multiple handy properties that you can use to test your feature.

import { getTestInstance } from "../test-utils";
 
const {
  client,
  auth,
  cookieSetter,
  customFetchImpl,
  db,
  sessionSetter,
  signInWithTestUser,
  signInWithUser,
  testUser,
} = getTestInstance();

Only create custom instances when needed

import { getTestInstance } from "../test-utils";
 
const { auth } = getTestInstance({
  customConfig: {
    // your custom configuration
  },
});

Best Practices

Do's

  • Write descriptive test names that explain the expected behavior
  • Test both success and failure cases
  • Keep tests focused and atomic
  • Use setup and teardown when needed
  • Test edge cases and boundary conditions

This is just example code, and isn't meant to be used as-is. It's meant to demonstrate the best Practices of testing.

describe("User Authentication", () => {
  it("should reject invalid credentials", async () => {
    const { client } = getTestInstance();
    const result = await client.signIn.email({
      email: "[email protected]",
      password: "wrong",
    });
 
    expect(result.success).toBe(false);
    expect(result.error).toBeDefined();
  });
 
  it("should handle empty inputs appropriately", async () => {
    const { client } = getTestInstance();
    const result = await client.signIn.email({
      email: "",
      password: "",
    });
 
    expect(result.success).toBe(false);
    expect(result.error).toMatch(/required/);
  });
});

Don'ts

  • Don't create unnecessary mocks
  • Don't test implementation details
  • Don't create separate test folders
  • Don't write brittle tests
  • Don't test external libraries

Future Plans

  • Implement integration testing
  • Add end-to-end testing
  • Expand API testing coverage
  • Add performance testing
  • Implement snapshot testing where appropriate

Contributing

When submitting a PR:

  • Ensure all tests pass
  • Add tests for new features
  • Update tests for modified features
  • Follow existing test patterns
  • Include meaningful assertions

Need help? Feel free to reach out in our Discord server!

On this page