Skip to content

Testing Guide

Learn how to test your Veloce-TS applications effectively.

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.

Terminal window
# Tests are run with Bun
bun test
# Watch mode
bun test --watch
# Coverage
bun test --coverage

Creates a test instance of your application.

import { createTestApp } from 'veloce-ts/testing';
const app = createTestApp();

HTTP client for making test requests.

import { TestClient } from 'veloce-ts/testing';
const client = new TestClient(app);
// Make requests
const response = await client.get('/users');
const data = await response.json();

Mocks a dependency in the DI container.

import { mockDependency } from 'veloce-ts/testing';
const mockDb = {
getUsers: async () => [{ id: 1, name: 'Test' }],
};
mockDependency(DatabaseService, mockDb);
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);
});
});
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();
});
});
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);
});
});
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');
});
});
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);
});
});
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);
});
});
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');
});
});
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();
});
});
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();
});
});
// ✓ Good
it('should return 404 when user does not exist', async () => {});
// ✗ Bad
it('test user', async () => {});
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);
});
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
});
});
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
});
});
fixtures/users.ts
export const testUsers = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
];
// test file
import { 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);
});
});
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');
});
it('should return correct user structure', async () => {
const response = await client.get('/users/1');
const data = await response.json();
expect(data).toMatchSnapshot();
});
Terminal window
bun test
Terminal window
bun test user.test.ts
Terminal window
bun test --watch
Terminal window
bun test --coverage
Terminal window
bun test --verbose