🧪 Complete Testing Suite for Hono Applications
A comprehensive testing toolkit for Hono applications across Cloudflare Workers, Node.js, and Bun runtimes. Features HTTP test utilities, smart data factories, database adapters, and environment management - everything you need to test modern web applications.
• Multi-Runtime Support: Works seamlessly with Cloudflare Workers, Node.js, and Bun
• Environment-Agnostic: Adapters for D1, SQLite, in-memory, and Drizzle databases
• NPM Ready: Complete package structure for publishing and reuse across projects
- Interactive Test Runner 🎮 - CLI with selectable test categories and runtime detection
- Smart Data Factory 🏭 - Seeded random generation for consistent, realistic test data
- Test Store Management 🗄️ - Automatic cleanup and state isolation
- HTTP Test Client 🌐 - Full-featured API testing with session management
- Vitest Integration ⚡ - Built-in helpers for automatic test isolation
- Database Adapters 💾 - Support for multiple database providers with automatic detection
- Environment Detection 🔍 - Automatic runtime and capability detection
• Fast Unit Tests: Memory database for rapid development
• Integration Tests: SQLite for realistic database interactions
• E2E Tests: Full Cloudflare Workers environment for production-like testing
• Flexible Runtime Detection: Automatically adapts to your environment
bun add --dev @go-corp/test-suite// test/setup.ts
import { setupCloudflareWorkerTests } from '@go-corp/test-suite'
// Sets up console mocking and required environment variables
setupCloudflareWorkerTests()import { requestWithCookies, postJSON } from '@go-corp/test-suite'
import app from '../src/app'
test('user authentication flow', async () => {
// All requests automatically persist cookies
const { json: user } = await postJSON(app, '/api/auth/sign-in', {
body: { email: 'test@example.com', password: 'password' }
})
// Session cookie is automatically included in subsequent requests
const { json: profile } = await requestJSON(app, '/api/profile', {
method: 'GET'
}, { expected: 200 })
expect(profile.email).toBe('test@example.com')
})// Modern setup with enhanced features
import {
setupTestFramework,
TestDataFactory,
createHttpTestClient,
dbTest,
factoryTest
} from '@go-corp/test-suite'
import app from '../src/app'
// Configure test framework
setupTestFramework({
app,
database: 'sqlite',
isolation: 'test',
autoCleanup: true
})
// Database test with automatic cleanup
dbTest('user creation with database', async (db) => {
const factory = new TestDataFactory()
const userData = factory.user()
// Insert user into database
await db.insert(users).values(userData)
// Test API endpoint
const client = createHttpTestClient(app)
const { response, json } = await client.getJSON(`/api/users/${userData.id}`)
expect(response.status).toBe(200)
expect(json.email).toBe(userData.email)
})
// Factory test with seeded data
factoryTest('consistent test data', async (factory) => {
const user1 = factory.user()
const user2 = factory.user()
// Same seed produces consistent data across test runs
expect(user1.id).toBeDefined()
expect(user2.id).toBeDefined()
expect(user1.id).not.toBe(user2.id)
}, 12345) // Custom seedThe test runner supports enterprise-level configuration through multiple formats:
The test runner automatically searches for configuration in:
.gotestsuiterc(JSON).gotestsuiterc.json.gotestsuiterc.yamlor.gotestsuiterc.yml.gotestsuiterc.js,.gotestsuiterc.mjs,.gotestsuiterc.cjsgotestsuite.config.js,gotestsuite.config.mjs,gotestsuite.config.cjspackage.jsonunder the"gotestsuite"key
{
"runtime": "node",
"database": "sqlite",
"categories": ["unit", "integration"],
"parallel": true,
"maxWorkers": 4,
"timeout": 30000,
"reporter": "default",
"coverage": false,
"telemetry": {
"enabled": false
},
"notifications": {
"enabled": false,
"slack": {
"webhook": "https://hooks.slack.com/services/...",
"channel": "#test-results"
}
}
}Track test runner usage and performance metrics:
{
"telemetry": {
"enabled": true,
"endpoint": "https://api.yourcompany.com/telemetry",
"apiKey": "your-api-key"
}
}Get notified when tests complete:
{
"notifications": {
"enabled": true,
"slack": {
"webhook": "https://hooks.slack.com/services/...",
"channel": "#test-results"
},
"email": {
"smtp": "smtp.yourcompany.com",
"from": "tests@yourcompany.com",
"to": ["dev-team@yourcompany.com"]
}
}
}All configuration options can be overridden via CLI flags:
# Runtime and database
bun run test-runner --runtime node --database sqlite
# Test selection
bun run test-runner --categories unit,integration --reporter verbose
# Execution control
bun run test-runner --parallel --max-workers 8 --timeout 60000
# Output control
bun run test-runner --verbose --coverage --bail
# Watch mode for development
bun run test-runner --watch --categories unitSet environment variables for test execution:
{
"env": {
"NODE_ENV": "test",
"DEBUG": "app:*",
"DATABASE_URL": "test.db"
},
"envFile": ".env.test"
}Makes a request with automatic cookie persistence across test requests.
import { requestWithCookies, resetCookies } from '@go-corp/test-suite'
// Start with clean session
resetCookies()
// Login request sets session cookie
const loginRes = await requestWithCookies(app, '/api/auth/login', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ username: 'test', password: 'pass' })
})
// Subsequent requests automatically include session cookie
const profileRes = await requestWithCookies(app, '/api/profile')Request helper that automatically parses JSON and validates status codes.
// Expect 200 status (default)
const { res, json } = await requestJSON(app, '/api/users', {
method: 'GET'
})
// Expect specific status
const { json: error } = await requestJSON(app, '/api/invalid', {
method: 'GET'
}, { expected: 404 })
// Accept multiple status codes
const { json } = await requestJSON(app, '/api/endpoint', {
method: 'POST',
body: JSON.stringify(data)
}, { expected: [200, 201] })Convenience wrapper for POST requests with JSON body.
// Simple POST
const { json } = await postJSON(app, '/api/users', {
body: { name: 'John', email: 'john@example.com' }
})
// With status validation
const { json } = await postJSON(app, '/api/users', {
body: { name: 'John' },
expected: 201
})Use different jarKey values to isolate test sessions:
import { requestWithCookies, resetCookies } from '@go-corp/test-suite'
test('concurrent user sessions', async () => {
// User A login
await postJSON(app, '/api/auth/login', {
body: { username: 'userA' },
jarKey: 'sessionA'
})
// User B login
await postJSON(app, '/api/auth/login', {
body: { username: 'userB' },
jarKey: 'sessionB'
})
// Each user sees their own profile
const { json: profileA } = await requestJSON(app, '/api/profile', {}, {
jarKey: 'sessionA'
})
const { json: profileB } = await requestJSON(app, '/api/profile', {}, {
jarKey: 'sessionB'
})
expect(profileA.username).toBe('userA')
expect(profileB.username).toBe('userB')
})Create isolated test contexts with automatic cleanup:
import { createTestContext } from '@go-corp/test-suite'
test('isolated test context', async () => {
const ctx = createTestContext()
// All requests use isolated cookie jar
await ctx.postJSON(app, '/api/auth/login', {
body: { username: 'test' }
})
const { json } = await ctx.requestJSON(app, '/api/profile', {})
expect(json.username).toBe('test')
// Clean up (optional, happens automatically)
ctx.reset()
})Test email functionality with built-in assertion helpers.
import {
clearOutbox,
assertEmailSent,
extractVerificationLink
} from '@go-corp/test-suite'
test('password reset flow', async () => {
await clearOutbox(app)
// Trigger password reset
await postJSON(app, '/api/auth/reset-password', {
body: { email: 'user@example.com' }
})
// Assert email was sent
const email = await assertEmailSent(app, 'user@example.com')
expect(email.subject).toContain('Password Reset')
// Extract and use verification link
const resetLink = extractVerificationLink(email)
expect(resetLink).toBeTruthy()
// Test the verification link
const { json } = await requestJSON(app, resetLink!)
expect(json.message).toBe('Reset link verified')
})import {
getOutbox,
getLastEmail,
waitForEmail,
extractOTPCode
} from '@go-corp/test-suite'
test('email verification with OTP', async () => {
await clearOutbox(app)
// Trigger account creation
await postJSON(app, '/api/auth/register', {
body: { email: 'new@example.com' }
})
// Wait for email (useful for async operations)
const email = await waitForEmail(app, 'new@example.com', 3000)
// Extract OTP code
const otpCode = extractOTPCode(email)
expect(otpCode).toBeTruthy()
// Verify with OTP
const { json } = await postJSON(app, '/api/auth/verify-otp', {
body: { email: 'new@example.com', code: otpCode }
})
expect(json.verified).toBe(true)
})import { setupCloudflareWorkerTests } from '@go-corp/test-suite'
// Automatic setup with sensible defaults
setupCloudflareWorkerTests()import { setupTestEnvironment } from '@go-corp/test-suite'
setupTestEnvironment({
mockConsole: true,
consoleMethods: ['log', 'info', 'debug'],
env: {
API_KEY: 'test-key',
DATABASE_URL: 'test-db'
},
cleanup: [
() => {
// Custom cleanup logic
}
]
})import { ensureTestEnv, withTestEnv } from '@go-corp/test-suite'
// Ensure variables exist with defaults
ensureTestEnv({
RESEND_API_KEY: 'test-key',
NODE_ENV: 'test'
})
// Run test with specific environment
const testWithEnv = withTestEnv({
FEATURE_FLAG: 'enabled'
}, async () => {
// Test logic here
const { json } = await requestJSON(app, '/api/feature')
expect(json.enabled).toBe(true)
})
await testWithEnv()import { createTimeMock } from '@go-corp/test-suite'
test('time-sensitive operations', async () => {
const mockTime = createTimeMock(new Date('2024-01-01'))
mockTime.start()
// Create token that expires in 1 hour
const { json: token } = await postJSON(app, '/api/auth/token')
// Advance time by 2 hours
mockTime.advance(2 * 60 * 60 * 1000)
// Token should now be expired
const { json } = await requestJSON(app, '/api/auth/verify', {
headers: { authorization: \`Bearer \${token.access_token}\` }
}, { expected: 401 })
expect(json.error).toBe('Token expired')
mockTime.restore()
})import { uniqueEmail, uniqueUsername } from '@go-corp/test-suite'
test('user creation', async () => {
const email = uniqueEmail() // test+1@example.com
const username = uniqueUsername() // user1
await postJSON(app, '/api/users', {
body: { email, username }
})
})import { wait } from '@go-corp/test-suite'
test('async operations', async () => {
// Trigger async operation
await postJSON(app, '/api/process')
// Wait for processing
await wait(1000)
// Check result
const { json } = await requestJSON(app, '/api/status')
expect(json.status).toBe('completed')
})createDatabaseAdapter(type, config?)- Create database adapter for testsMemoryDatabaseAdapter,SqliteDatabaseAdapter,D1DatabaseAdapter- Database implementationsDrizzleSqliteAdapter,DrizzleD1Adapter- Drizzle ORM integrations
TestDataFactory- Seeded data generation for consistent testsfactory.user(),factory.organization(),factory.post()- Built-in generatorscreateSeededFactory(seed)- Factory with custom seed
HttpTestClient- Full-featured test client with retries and cookiescreateHttpTestClient(app, options)- Create HTTP client instance
setupTestFramework(config)- Configure test environmenttestSuite(name, fn, config)- Enhanced test suite with setuptestWithContext(name, fn)- Test with isolated contextdbTest(name, fn)- Database test with cleanuphttpTest(name, fn)- HTTP test with session isolationfactoryTest(name, fn, seed?)- Test with seeded data factory
requestWithCookies(app, path, init?, jarKey?)- Make request with cookie persistencerequestJSON(app, path, init, options?)- Request with JSON parsing and status validationpostJSON(app, path, options?)- POST request convenience wrapperresetCookies(jarKey?)- Clear cookies for jar key or all jarscreateTestContext(jarKey?)- Create isolated test context
getOutbox(app, jarKey?)- Get all emails from test outboxclearOutbox(app, jarKey?)- Clear test email outboxgetLastEmail(app, recipient, jarKey?)- Get most recent email to recipientwaitForEmail(app, recipient, timeout?, jarKey?)- Wait for email to be sentassertEmailSent(app, recipient, jarKey?)- Assert email was sentextractVerificationLink(email)- Extract verification URL from emailextractOTPCode(email)- Extract OTP code from email
setupCloudflareWorkerTests()- Default setup for Cloudflare WorkerssetupTestEnvironment(options?)- Custom test environment setupensureTestEnv(vars)- Ensure environment variables existwithTestEnv(env, fn)- Run function with specific environment
uniqueEmail(prefix?)- Generate unique email addressuniqueUsername(prefix?)- Generate unique usernamewait(ms)- Wait for specified millisecondscreateTimeMock(date?)- Create time mock for testing
The package includes comprehensive TypeScript definitions for all APIs:
// Core types
import type {
Runtime,
DatabaseProvider,
TestEnvironmentConfig,
HonoApp
} from '@go-corp/test-suite'
// HTTP testing types
import type {
TestRequestOptions,
TestResponse,
HttpClientOptions
} from '@go-corp/test-suite'
// Email testing types
import type {
TestEmail,
TestSetupOptions
} from '@go-corp/test-suite'
// Factory types
import type {
FactoryConfig,
FactoryFunction,
TestDataGenerators
} from '@go-corp/test-suite'
// Store and lifecycle types
import type {
TestStore,
IsolationLevel,
VitestConfig,
TestSuiteConfig,
TestRunnerConfig
} from '@go-corp/test-suite'
// Database adapter type
import type {
DatabaseAdapter
} from '@go-corp/test-suite'# Install dependencies
bun install
# Build for development
bun run dev
# Type check
bun run typecheck
# Lint code
bun run lint
bun run lint:fix
# Test the CLI
bun run test-runner --helpThis project uses @go-corp/workflow for automated release management:
# Interactive release (quality gates, git, npm)
bun run release
# Dry run (show what would happen)
bun run release --dry-run
# Skip specific deployments
bun run release --skip-npm --skip-cloudflare
# Force specific version bump
bun run release --type minor
# Check workflow status
bun run workflow:statusRelease Pipeline:
- ✅ Quality Gates: Auto-fix linting, type checking, tests
- 🏷️ Version Management: Smart semantic versioning with changelog
- 📦 Build & Publish: Automated npm publishing with confirmation
- 🔗 GitHub Integration: Automated releases and tagging
MIT