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 utilitiesimport { ... } from '@kit/database/testing';// PGLite-specific utilitiesimport { 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:
membersubscriptionorganizationuseraccountsessionverificationinvitationtwo_factororganization_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
| Variable | Description | Default |
|---|---|---|
TEST_DATABASE_URL | Test database connection string (preferred) | - |
DATABASE_URL | Fallback connection string | - |
| - | Default if neither is set | postgresql://postgres:postgres@localhost:5432/postgres |
Factory Functions
Generate test data with sensible defaults:
createTestUser()
import { createTestUser } from '@kit/database/testing';// With defaultsconst user = createTestUser();// { id: 'abc123...', email: 'test-abc1@makerkit.dev', name: 'Test User abc1', ... }// With overridesconst admin = createTestUser({ email: 'admin@example.com', name: 'Admin User',});Default Values:
| Field | Default |
|---|---|
id | Random 32-character hex string |
email | test-{id}@makerkit.dev |
emailVerified | true |
name | Test User {first 4 chars of id} |
image | null |
createdAt | Current date |
updatedAt | Current date |
createTestOrganization()
import { createTestOrganization } from '@kit/database/testing';const org = createTestOrganization({ name: 'Acme Corp', slug: 'acme-corp',});Default Values:
| Field | Default |
|---|---|
id | Random 32-character hex string |
name | Test Organization {first 4 chars of id} |
slug | test-org-{id} |
logo | null |
metadata | "{}" |
createdAt | Current 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:
| Field | Default |
|---|---|
id | Random 32-character hex string |
userId | Empty string (must override) |
organizationId | Empty string (must override) |
role | 'member' |
createdAt | Current 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:
| Field | Default |
|---|---|
id | Random 32-character hex string |
plan | 'pro-monthly' |
referenceId | Empty string (should override) |
customer_id | cus_test_{id} |
subscription_id | sub_test_{id} |
status | 'active' |
periodStart | Current date |
periodEnd | 30 days from now |
cancelAtPeriodEnd | false |
seats | 1 |
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 queriesconst 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 organizationconst projects = await db.select() .from(projectsTable) .where(ctx.org(projectsTable)); // eq(projectsTable.organizationId, 'org_456')// Auto-fill organizationId on insertawait 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 errorsthrow 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.tsimport { testDb } from '@kit/database/testing';// testDb is automatically initialized before all testsexport { testDb };Configure in vitest.config.ts:
export default defineConfig({ test: { setupFiles: ['./vitest.setup.ts'], },});Previous: SQLite