Testing Guide
Testing Guide
Section titled “Testing Guide”Learn how to test your Veloce-TS applications effectively.
Table of Contents
Section titled “Table of Contents”- Introduction
- Testing Utilities
- Unit Testing
- Integration Testing
- Testing with Dependencies
- Testing WebSockets
- Testing GraphQL
- Best Practices
Introduction
Section titled “Introduction”Veloce provides testing utilities to make it easy to test your applications. The framework uses Bun’s built-in test runner, which is fast and has a Jest-compatible API.
# Tests are run with Bunbun test
# Watch modebun test --watch
# Coveragebun test --coverageTesting Utilities
Section titled “Testing Utilities”createTestApp()
Section titled “createTestApp()”Creates a test instance of your application.
import { createTestApp } from 'veloce-ts/testing';
const app = createTestApp();TestClient
Section titled “TestClient”HTTP client for making test requests.
import { TestClient } from 'veloce-ts/testing';
const client = new TestClient(app);
// Make requestsconst response = await client.get('/users');const data = await response.json();mockDependency()
Section titled “mockDependency()”Mocks a dependency in the DI container.
import { mockDependency } from 'veloce-ts/testing';
const mockDb = { getUsers: async () => [{ id: 1, name: 'Test' }],};
mockDependency(DatabaseService, mockDb);Unit Testing
Section titled “Unit Testing”Testing Controllers
Section titled “Testing Controllers”import { describe, it, expect, beforeEach } from 'bun:test';import { createTestApp, TestClient } from 'veloce-ts/testing';import { UserController } from './controllers/user.controller';
describe('UserController', () => { let app: Veloce; let client: TestClient;
beforeEach(() => { app = createTestApp(); app.include(UserController); client = new TestClient(app); });
it('should get all users', async () => { const response = await client.get('/users');
expect(response.status).toBe(200);
const data = await response.json(); expect(data).toBeArray(); });
it('should get user by id', async () => { const response = await client.get('/users/1');
expect(response.status).toBe(200);
const data = await response.json(); expect(data).toHaveProperty('id'); expect(data.id).toBe('1'); });
it('should create user', async () => { const userData = { name: 'John Doe', email: 'john@example.com', };
const response = await client.post('/users', userData);
expect(response.status).toBe(201);
const data = await response.json(); expect(data.name).toBe(userData.name); expect(data.email).toBe(userData.email); });
it('should validate user data', async () => { const invalidData = { name: 'J', // Too short email: 'invalid-email', };
const response = await client.post('/users', invalidData);
expect(response.status).toBe(422);
const data = await response.json(); expect(data).toHaveProperty('error'); });
it('should return 404 for non-existent user', async () => { const response = await client.get('/users/999');
expect(response.status).toBe(404); });});Testing Services
Section titled “Testing Services”import { describe, it, expect } from 'bun:test';import { UserService } from './services/user.service';
describe('UserService', () => { it('should get users', async () => { const service = new UserService(); const users = await service.getUsers();
expect(users).toBeArray(); });
it('should find user by id', async () => { const service = new UserService(); const user = await service.getUserById('1');
expect(user).toBeDefined(); expect(user?.id).toBe('1'); });
it('should return null for non-existent user', async () => { const service = new UserService(); const user = await service.getUserById('999');
expect(user).toBeNull(); });});Testing Validation
Section titled “Testing Validation”import { describe, it, expect } from 'bun:test';import { z } from 'zod';
const UserSchema = z.object({ name: z.string().min(2), email: z.string().email(), age: z.number().min(18).optional(),});
describe('UserSchema', () => { it('should validate valid user', () => { const validUser = { name: 'John Doe', email: 'john@example.com', age: 30, };
const result = UserSchema.safeParse(validUser); expect(result.success).toBe(true); });
it('should reject invalid email', () => { const invalidUser = { name: 'John Doe', email: 'invalid-email', };
const result = UserSchema.safeParse(invalidUser); expect(result.success).toBe(false); });
it('should reject short name', () => { const invalidUser = { name: 'J', email: 'john@example.com', };
const result = UserSchema.safeParse(invalidUser); expect(result.success).toBe(false); });
it('should reject underage user', () => { const invalidUser = { name: 'John Doe', email: 'john@example.com', age: 17, };
const result = UserSchema.safeParse(invalidUser); expect(result.success).toBe(false); });});Integration Testing
Section titled “Integration Testing”Testing Full Application
Section titled “Testing Full Application”import { describe, it, expect, beforeAll, afterAll } from 'bun:test';import { Veloce } from 'veloce-ts';import { UserController } from './controllers/user.controller';import { DatabaseService } from './services/database.service';
describe('Integration Tests', () => { let app: Veloce; let server: any;
beforeAll(async () => { app = new Veloce();
// Setup database const db = new DatabaseService(); await db.connect();
app.getContainer().register(DatabaseService, { scope: 'singleton', factory: () => db, });
app.include(UserController);
server = app.listen(3001); });
afterAll(async () => { server.close(); });
it('should create and retrieve user', async () => { // Create user const createResponse = await fetch('http://localhost:3001/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Integration Test User', email: 'test@example.com', }), });
expect(createResponse.status).toBe(201); const createdUser = await createResponse.json();
// Retrieve user const getResponse = await fetch(`http://localhost:3001/users/${createdUser.id}`); expect(getResponse.status).toBe(200);
const retrievedUser = await getResponse.json(); expect(retrievedUser.id).toBe(createdUser.id); expect(retrievedUser.name).toBe('Integration Test User'); });});Testing Middleware
Section titled “Testing Middleware”import { describe, it, expect } from 'bun:test';import { createTestApp, TestClient } from 'veloce-ts/testing';
const authMiddleware = async (c: any, next: any) => { const token = c.req.header('authorization'); if (!token) { return c.json({ error: 'Unauthorized' }, 401); } await next();};
describe('Auth Middleware', () => { it('should block requests without token', async () => { const app = createTestApp(); app.use(authMiddleware);
app.get('/protected', { handler: async (c) => { return { data: 'secret' }; }, });
const client = new TestClient(app); const response = await client.get('/protected');
expect(response.status).toBe(401); });
it('should allow requests with token', async () => { const app = createTestApp(); app.use(authMiddleware);
app.get('/protected', { handler: async (c) => { return { data: 'secret' }; }, });
const client = new TestClient(app); const response = await client.get('/protected', { headers: { authorization: 'Bearer token' }, });
expect(response.status).toBe(200); });});Testing with Dependencies
Section titled “Testing with Dependencies”Mocking Dependencies
Section titled “Mocking Dependencies”import { describe, it, expect, beforeEach } from 'bun:test';import { createTestApp, mockDependency, TestClient } from 'veloce-ts/testing';
class DatabaseService { async getUsers() { return []; }}
describe('UserController with Mocks', () => { let app: Veloce; let client: TestClient; let mockDb: any;
beforeEach(() => { app = createTestApp();
// Create mock mockDb = { getUsers: async () => [ { id: 1, name: 'Mock User 1' }, { id: 2, name: 'Mock User 2' }, ], getUserById: async (id: string) => { return { id: parseInt(id), name: `Mock User ${id}` }; }, createUser: async (user: any) => { return { id: 3, ...user }; }, };
// Replace real service with mock mockDependency(DatabaseService, mockDb);
app.include(UserController); client = new TestClient(app); });
it('should use mocked database', async () => { const response = await client.get('/users'); const data = await response.json();
expect(data).toHaveLength(2); expect(data[0].name).toBe('Mock User 1'); });
it('should create user with mock', async () => { const response = await client.post('/users', { name: 'New User', email: 'new@example.com', });
const data = await response.json(); expect(data.id).toBe(3); });});Testing with Real Dependencies
Section titled “Testing with Real Dependencies”import { describe, it, expect, beforeEach } from 'bun:test';import { createTestApp, TestClient } from 'veloce-ts/testing';
describe('UserController with Real Database', () => { let app: Veloce; let client: TestClient; let db: DatabaseService;
beforeEach(async () => { app = createTestApp();
// Use in-memory database for testing db = new InMemoryDatabaseService(); await db.connect();
app.getContainer().register(DatabaseService, { scope: 'singleton', factory: () => db, });
app.include(UserController); client = new TestClient(app); });
it('should persist data', async () => { // Create user const createResponse = await client.post('/users', { name: 'Test User', email: 'test@example.com', }); const created = await createResponse.json();
// Retrieve user const getResponse = await client.get(`/users/${created.id}`); const retrieved = await getResponse.json();
expect(retrieved.id).toBe(created.id); expect(retrieved.name).toBe('Test User'); });});Testing WebSockets
Section titled “Testing WebSockets”import { describe, it, expect } from 'bun:test';import { createTestApp } from 'veloce-ts/testing';
describe('WebSocket Handler', () => { it('should handle connections', async () => { const app = createTestApp(); app.include(ChatHandler);
// Create WebSocket client const ws = new WebSocket('ws://localhost:3000/chat');
await new Promise((resolve) => { ws.onopen = resolve; });
expect(ws.readyState).toBe(WebSocket.OPEN);
ws.close(); });
it('should receive messages', async () => { const app = createTestApp(); app.include(ChatHandler);
const ws = new WebSocket('ws://localhost:3000/chat');
const message = await new Promise((resolve) => { ws.onmessage = (event) => { resolve(JSON.parse(event.data)); }; });
expect(message).toHaveProperty('type');
ws.close(); });
it('should broadcast messages', async () => { const app = createTestApp(); app.include(ChatHandler);
// Create two clients const ws1 = new WebSocket('ws://localhost:3000/chat'); const ws2 = new WebSocket('ws://localhost:3000/chat');
await Promise.all([ new Promise((resolve) => ws1.onopen = resolve), new Promise((resolve) => ws2.onopen = resolve), ]);
// Send message from client 1 ws1.send(JSON.stringify({ type: 'message', content: 'Hello', username: 'User1', }));
// Client 2 should receive it const received = await new Promise((resolve) => { ws2.onmessage = (event) => { resolve(JSON.parse(event.data)); }; });
expect(received).toMatchObject({ type: 'message', content: 'Hello', });
ws1.close(); ws2.close(); });});Testing GraphQL
Section titled “Testing GraphQL”import { describe, it, expect } from 'bun:test';import { createTestApp, TestClient } from 'veloce-ts/testing';
describe('GraphQL Resolver', () => { let app: Veloce; let client: TestClient;
beforeEach(() => { app = createTestApp(); app.usePlugin(new GraphQLPlugin()); app.include(UserResolver); client = new TestClient(app); });
it('should execute query', async () => { const query = ` query { users { id name email } } `;
const response = await client.post('/graphql', { query }); const data = await response.json();
expect(data.data.users).toBeArray(); });
it('should execute mutation', async () => { const mutation = ` mutation { createUser(input: { name: "Test User" email: "test@example.com" }) { id name email } } `;
const response = await client.post('/graphql', { query: mutation }); const data = await response.json();
expect(data.data.createUser.name).toBe('Test User'); });
it('should validate arguments', async () => { const mutation = ` mutation { createUser(input: { name: "T" email: "invalid-email" }) { id } } `;
const response = await client.post('/graphql', { query: mutation }); const data = await response.json();
expect(data.errors).toBeDefined(); });});Best Practices
Section titled “Best Practices”1. Use Descriptive Test Names
Section titled “1. Use Descriptive Test Names”// ✓ Goodit('should return 404 when user does not exist', async () => {});
// ✗ Badit('test user', async () => {});2. Arrange-Act-Assert Pattern
Section titled “2. Arrange-Act-Assert Pattern”it('should create user', async () => { // Arrange const userData = { name: 'John Doe', email: 'john@example.com', };
// Act const response = await client.post('/users', userData); const data = await response.json();
// Assert expect(response.status).toBe(201); expect(data.name).toBe(userData.name);});3. Test Edge Cases
Section titled “3. Test Edge Cases”describe('UserController', () => { it('should handle empty list', async () => { // Test when no users exist });
it('should handle invalid ID format', async () => { // Test with non-numeric ID });
it('should handle missing required fields', async () => { // Test validation });
it('should handle duplicate email', async () => { // Test unique constraint });});4. Clean Up After Tests
Section titled “4. Clean Up After Tests”describe('UserController', () => { let db: DatabaseService;
beforeEach(async () => { db = new InMemoryDatabaseService(); await db.connect(); });
afterEach(async () => { await db.clear(); await db.disconnect(); });
it('should create user', async () => { // Test });});5. Use Test Fixtures
Section titled “5. Use Test Fixtures”export const testUsers = [ { id: 1, name: 'John Doe', email: 'john@example.com' }, { id: 2, name: 'Jane Smith', email: 'jane@example.com' },];
// test fileimport { testUsers } from './fixtures/users';
describe('UserController', () => { beforeEach(() => { mockDb.getUsers = async () => testUsers; });
it('should get users', async () => { const response = await client.get('/users'); const data = await response.json();
expect(data).toEqual(testUsers); });});6. Test Error Handling
Section titled “6. Test Error Handling”it('should handle database errors', async () => { mockDb.getUsers = async () => { throw new Error('Database connection failed'); };
const response = await client.get('/users');
expect(response.status).toBe(500);
const data = await response.json(); expect(data).toHaveProperty('error');});7. Use Snapshots for Complex Responses
Section titled “7. Use Snapshots for Complex Responses”it('should return correct user structure', async () => { const response = await client.get('/users/1'); const data = await response.json();
expect(data).toMatchSnapshot();});Running Tests
Section titled “Running Tests”Run All Tests
Section titled “Run All Tests”bun testRun Specific Test File
Section titled “Run Specific Test File”bun test user.test.tsWatch Mode
Section titled “Watch Mode”bun test --watchCoverage
Section titled “Coverage”bun test --coverageVerbose Output
Section titled “Verbose Output”bun test --verboseNext Steps
Section titled “Next Steps”- Check out the Getting Started Guide
- Learn about Dependency Injection
- Read the API Reference