Testing
Tests are written with Vitest and run against a real PostgreSQL database. Each test suite spins up a full Fastify app with the plugin registered, runs tests, then tears it down.
Requirements
- PostgreSQL running locally (or via Docker)
- A dedicated test database (separate from your dev database)
docker compose up postgres -dRunning Tests
From the project root:
pnpm test # run all tests oncepnpm test:watch # re-run on file changespnpm test:coverage # run with coverage reportpnpm test:ui # open Vitest UI in the browserOr from the fastify-admin/ directory directly:
cd fastify-adminpnpm testTest Database
Tests use a separate database to avoid corrupting dev data. Set these environment variables (or add them to fastify-admin/.env):
TEST_DB_NAME=fastifyadmin_testDB_HOST=localhostDB_PORT=5432DB_USER=postgresDB_PASSWORD=passwordEach test suite calls orm.schema.refreshDatabase() in beforeAll — this drops and recreates all tables so every suite starts clean.
Test Structure
Tests live in fastify-admin/src/tests/:
tests/ setup.ts ← shared buildApp() / teardown() helpers auth.test.ts ← login, signup, logout, sessions email-verification.test.ts ← email verification flow oauth.test.ts ← Google / GitHub / Microsoft OAuth entities.test.ts ← CRUD endpoints for entities permissions.test.ts ← RBAC permission checks plugin.test.ts ← plugin options and resource config entity-view.test.ts ← EntityView class behaviour cli.test.ts ← create-admin / reset-password CLI mailer.test.ts ← email sending helpers password.test.ts ← password hashing utilities db.test.ts ← database connection helpers generate-username.test.ts ← username generation logicWriting a Test
Use the shared buildApp() helper to get a running app instance:
import { describe, it, expect, beforeAll, afterAll } from 'vitest'import { buildApp, teardown } from './setup.js'
let ctx: Awaited<ReturnType<typeof buildApp>>
beforeAll(async () => { ctx = await buildApp()})
afterAll(async () => { await teardown(ctx)})
describe('my feature', () => { it('returns 200', async () => { const res = await ctx.app.inject({ method: 'GET', url: '/api/admin-config', }) expect(res.statusCode).toBe(200) })})buildApp() options
buildApp({ requireEmailVerification: false, // enable email verification flow mfaEnabled: false, // enable SMTP email sending views: { // custom entity view config role: { label: 'Team Roles', sidebar: false }, },})Making authenticated requests
Most endpoints require a logged-in session. Log in first and pass the cookie:
const login = await ctx.app.inject({ method: 'POST', url: '/api/auth/login', payload: { email: 'admin@example.com', password: 'password123' },})
const cookie = login.headers['set-cookie'] as string
const res = await ctx.app.inject({ method: 'GET', url: '/api/entities/post', headers: { cookie },})