Testing Utilities

Database testing utilities for unit and integration tests

The @kit/database package includes comprehensive testing utilities for writing unit and integration tests with real database interactions.

Overview

The testing utilities provide:

  • In-memory PostgreSQL database using PGLite
  • Real PostgreSQL connection for integration tests
  • Factory functions for generating test data
  • Auth context utilities for multi-tenant testing

Installation

The testing utilities are included in @kit/database. Import from:

// Main testing utilities
import { ... } from '@kit/database/testing';
// PGLite-specific utilities
import { createTestDatabase } from '@kit/database/testing/pglite';

In-Memory Database (PGLite)

PGLite provides a WebAssembly-based PostgreSQL implementation for fast, isolated testing without external dependencies.

createTestDatabase()

Creates an in-memory PostgreSQL database with the full schema applied.

import { createTestDatabase, type TestDatabase } from '@kit/database/testing/pglite';
describe('User Service', () => {
let testDb: TestDatabase;
beforeAll(async () => {
testDb = await createTestDatabase();
});
afterEach(async () => {
await testDb.cleanup(); // Clear all data between tests
});
afterAll(async () => {
await testDb.close(); // Close connection
});
it('should create a user', async () => {
const result = await testDb.db.insert(users).values({
id: 'user_123',
email: 'test@example.com',
name: 'Test User',
}).returning();
expect(result[0].email).toBe('test@example.com');
});
});

TestDatabase Interface

interface TestDatabase {
db: DrizzleInstance; // Drizzle ORM instance
client: PGlite; // Raw PGLite client
cleanup: () => Promise<void>; // Truncate all tables
close: () => Promise<void>; // Close connection
}

Tables Cleared by cleanup()

The cleanup() method truncates these tables in order:

  • member
  • subscription
  • organization
  • user
  • account
  • session
  • verification
  • invitation
  • two_factor
  • organization_role

Real PostgreSQL Testing

For integration tests requiring a real PostgreSQL instance:

setupTestDatabase()

import { setupTestDatabase } from '@kit/database/testing';
let testDb: Awaited<ReturnType<typeof setupTestDatabase>>;
beforeAll(async () => {
testDb = await setupTestDatabase();
});
afterEach(async () => {
await testDb.cleanup();
});
afterAll(async () => {
await testDb.teardown();
});

Environment Variables

VariableDescriptionDefault
TEST_DATABASE_URLTest database connection string (preferred)-
DATABASE_URLFallback connection string-
-Default if neither is setpostgresql://postgres:postgres@localhost:5432/postgres

Factory Functions

Generate test data with sensible defaults:

createTestUser()

import { createTestUser } from '@kit/database/testing';
// With defaults
const user = createTestUser();
// { id: 'abc123...', email: 'test-abc1@makerkit.dev', name: 'Test User abc1', ... }
// With overrides
const admin = createTestUser({
email: 'admin@example.com',
name: 'Admin User',
});

Default Values:

FieldDefault
idRandom 32-character hex string
emailtest-{id}@makerkit.dev
emailVerifiedtrue
nameTest User {first 4 chars of id}
imagenull
createdAtCurrent date
updatedAtCurrent date

createTestOrganization()

import { createTestOrganization } from '@kit/database/testing';
const org = createTestOrganization({
name: 'Acme Corp',
slug: 'acme-corp',
});

Default Values:

FieldDefault
idRandom 32-character hex string
nameTest Organization {first 4 chars of id}
slugtest-org-{id}
logonull
metadata"{}"
createdAtCurrent date

createTestMember()

import { createTestUser, createTestOrganization, createTestMember } from '@kit/database/testing';
const user = createTestUser();
const org = createTestOrganization();
const member = createTestMember({
userId: user.id, // Required
organizationId: org.id, // Required
role: 'owner',
});

Default Values:

FieldDefault
idRandom 32-character hex string
userIdEmpty string (must override)
organizationIdEmpty string (must override)
role'member'
createdAtCurrent date

createTestSubscription()

import { createTestOrganization, createTestSubscription } from '@kit/database/testing';
const org = createTestOrganization();
const subscription = createTestSubscription({
referenceId: org.id,
plan: 'enterprise-annual',
seats: 10,
});

Default Values:

FieldDefault
idRandom 32-character hex string
plan'pro-monthly'
referenceIdEmpty string (should override)
customer_idcus_test_{id}
subscription_idsub_test_{id}
status'active'
periodStartCurrent date
periodEnd30 days from now
cancelAtPeriodEndfalse
seats1

Auth Context Utilities

Utilities for testing multi-tenant authorization:

createAuthContext()

Creates a basic authorization context for an authenticated user:

import { createAuthContext } from '@kit/database';
const ctx = createAuthContext(
'user_123', // userId
null, // organizationId (optional)
null // role (optional)
);
// Use in queries
const posts = await db.select()
.from(postsTable)
.where(ctx.user(postsTable)); // eq(postsTable.userId, 'user_123')

createOrgAuthContext()

Creates an organization-scoped authorization context:

import { createOrgAuthContext } from '@kit/database';
const ctx = createOrgAuthContext(
'user_123', // userId
'org_456', // organizationId (required)
'admin' // role (required)
);
// Filter by organization
const projects = await db.select()
.from(projectsTable)
.where(ctx.org(projectsTable)); // eq(projectsTable.organizationId, 'org_456')
// Auto-fill organizationId on insert
await db.insert(projectsTable).values(
ctx.values(projectsTable, {
name: 'New Project',
// organizationId is automatically added!
})
);

AuthorizationError

Custom error class for authorization failures:

import { AuthorizationError } from '@kit/database';
// Throw specific authorization errors
throw AuthorizationError.notAuthenticated();
// Error: "Not authenticated"
throw AuthorizationError.noOrganization();
// Error: "No active organization"
throw AuthorizationError.notAuthorized('project');
// Error: "Not authorized to access project"

Complete Example

import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import {
createTestDatabase,
createTestUser,
createTestOrganization,
createTestMember,
createTestSubscription,
} from '@kit/database/testing';
import { createOrgAuthContext } from '@kit/database';
import { user, organization, member, subscription } from '@kit/database';
describe('Organization Billing', () => {
let testDb: Awaited<ReturnType<typeof createTestDatabase>>;
beforeAll(async () => {
testDb = await createTestDatabase();
});
afterEach(async () => {
await testDb.cleanup();
});
afterAll(async () => {
await testDb.close();
});
it('should create organization with subscription', async () => {
// Create test data
const testUser = createTestUser();
const testOrg = createTestOrganization();
const testMember = createTestMember({
userId: testUser.id,
organizationId: testOrg.id,
role: 'owner',
});
const testSub = createTestSubscription({
referenceId: testOrg.id,
plan: 'pro-monthly',
});
// Insert into database
await testDb.db.insert(user).values(testUser);
await testDb.db.insert(organization).values(testOrg);
await testDb.db.insert(member).values(testMember);
await testDb.db.insert(subscription).values(testSub);
// Create auth context
const ctx = createOrgAuthContext(testUser.id, testOrg.id, 'owner');
// Query with auth context
const [result] = await testDb.db.select()
.from(subscription)
.where(ctx.org(subscription));
expect(result.plan).toBe('pro-monthly');
expect(result.status).toBe('active');
});
});

Vitest Setup

For automatic database setup in all tests, use the setup file:

// vitest.setup.ts
import { testDb } from '@kit/database/testing';
// testDb is automatically initialized before all tests
export { testDb };

Configure in vitest.config.ts:

export default defineConfig({
test: {
setupFiles: ['./vitest.setup.ts'],
},
});

Previous: SQLite