Testing Guide
Testing strategies for MoroJS applications using standard testing libraries. Learn unit testing, integration testing, and API testing patterns.
Testing Setup
Basic Testing Setup
typescript
1// package.json - Testing dependencies
2{
3 "devDependencies": {
4 "@types/jest": "^29.5.0",
5 "@types/supertest": "^2.0.12",
6 "jest": "^29.5.0",
7 "supertest": "^6.3.0",
8 "ts-jest": "^29.1.0"
9 },
10 "scripts": {
11 "test": "jest",
12 "test:watch": "jest --watch"
13 }
14}
15
16// jest.config.js - Simple configuration
17module.exports = {
18 preset: 'ts-jest',
19 testEnvironment: 'node',
20 roots: ['<rootDir>/src', '<rootDir>/tests'],
21 testMatch: ['**/__tests__/**/*.test.ts', '**/?(*.)+(spec|test).ts'],
22 collectCoverageFrom: [
23 'src/**/*.ts',
24 '!src/**/*.d.ts'
25 ],
26 coverageDirectory: 'coverage',
27 testTimeout: 10000
28};
29
30// tests/setup.ts - Simple setup
31import { createApp } from '@morojs/moro';
32
33// Helper to create test app
34export function createTestApp() {
35 const app = createApp({
36 cors: true,
37 compression: false, // Disable for testing
38 helmet: false // Disable for testing
39 });
40
41 return app;
42}
Unit Testing
Service Unit Tests
typescript
1// services/__tests__/UserService.test.ts
2import { UserService } from '../UserService';
3import { createMockDatabase, createMockEventBus } from '../../tests/mocks';
4
5describe('UserService', () => {
6 let userService: UserService;
7 let mockDb: any;
8 let mockEvents: any;
9
10 beforeEach(() => {
11 mockDb = createMockDatabase();
12 mockEvents = createMockEventBus();
13 userService = new UserService(mockDb, mockEvents);
14 });
15
16 describe('createUser', () => {
17 it('should create a user successfully', async () => {
18 const userData = { email: 'test@example.com', name: 'Test User' };
19 const expectedUser = { id: '1', ...userData, created_at: new Date() };
20
21 mockDb.query.mockResolvedValueOnce([expectedUser]);
22
23 const result = await userService.createUser(userData);
24
25 expect(result).toEqual(expectedUser);
26 expect(mockDb.query).toHaveBeenCalledWith(
27 expect.stringContaining('INSERT INTO users'),
28 [userData.email, userData.name, expect.any(Date)]
29 );
30 expect(mockEvents.emit).toHaveBeenCalledWith('user.created', { user: expectedUser });
31 });
32
33 it('should validate email format', async () => {
34 const userData = { email: 'invalid-email', name: 'Test User' };
35
36 await expect(userService.createUser(userData)).rejects.toThrow('Invalid email');
37 });
38
39 it('should handle database errors', async () => {
40 const userData = { email: 'test@example.com', name: 'Test User' };
41
42 mockDb.query.mockRejectedValueOnce(new Error('Database connection failed'));
43
44 await expect(userService.createUser(userData)).rejects.toThrow('Database connection failed');
45 });
46 });
47
48 describe('getUserById', () => {
49 it('should return user when found', async () => {
50 const userId = '123';
51 const expectedUser = { id: userId, email: 'test@example.com', name: 'Test User' };
52
53 mockDb.query.mockResolvedValueOnce([expectedUser]);
54
55 const result = await userService.getUserById(userId);
56
57 expect(result).toEqual(expectedUser);
58 expect(mockDb.query).toHaveBeenCalledWith(
59 'SELECT * FROM users WHERE id = $1',
60 [userId]
61 );
62 });
63
64 it('should return null when user not found', async () => {
65 mockDb.query.mockResolvedValueOnce([]);
66
67 const result = await userService.getUserById('nonexistent');
68
69 expect(result).toBeNull();
70 });
71 });
72});
Integration Testing
Simple API Testing
typescript
1// tests/api.test.ts - Simple API testing
2import request from 'supertest';
3import { createApp, z } from '@morojs/moro';
4
5describe('MoroJS API Tests', () => {
6 let app: any;
7 let server: any;
8
9 beforeAll(async () => {
10 // Create test app
11 app = createApp({
12 cors: true,
13 compression: false,
14 helmet: false
15 });
16
17 // In-memory test data
18 const users = [
19 { id: 1, name: 'John Doe', email: 'john@example.com' },
20 { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
21 ];
22
23 // Test routes
24 app.get('/users', (req, res) => {
25 return { success: true, data: users };
26 });
27
28 app.post('/users')
29 .body(z.object({
30 name: z.string().min(2),
31 email: z.string().email()
32 }))
33 .handler((req, res) => {
34 const newUser = {
35 id: users.length + 1,
36 ...req.body
37 };
38 users.push(newUser);
39 res.status(201);
40 return { success: true, data: newUser };
41 });
42
43 app.get('/users/:id', (req, res) => {
44 const user = users.find(u => u.id === parseInt(req.params.id));
45 if (!user) {
46 res.status(404);
47 return { success: false, error: 'User not found' };
48 }
49 return { success: true, data: user };
50 });
51
52 // Start test server
53 server = app.listen(0); // Random port
54 });
55
56 afterAll(async () => {
57 server?.close();
58 });
59
60 describe('GET /users', () => {
61 it('should return list of users', async () => {
62 const response = await request(server)
63 .get('/users')
64 .expect(200);
65
66 expect(response.body).toMatchObject({
67 success: true,
68 data: expect.arrayContaining([
69 expect.objectContaining({
70 id: expect.any(Number),
71 name: expect.any(String),
72 email: expect.any(String)
73 })
74 ])
75 });
76 });
77 });
78
79 describe('POST /users', () => {
80 it('should create a user with valid data', async () => {
81 const userData = {
82 name: 'Test User',
83 email: 'test@example.com'
84 };
85
86 const response = await request(server)
87 .post('/users')
88 .send(userData)
89 .expect(201);
90
91 expect(response.body).toMatchObject({
92 success: true,
93 data: {
94 id: expect.any(Number),
95 name: userData.name,
96 email: userData.email
97 }
98 });
99 });
100
101 it('should return 400 for invalid email', async () => {
102 const userData = {
103 name: 'Test User',
104 email: 'invalid-email'
105 };
106
107 await request(server)
108 .post('/users')
109 .send(userData)
110 .expect(400);
111 });
112 });
113
114 describe('GET /users/:id', () => {
115 it('should return user when found', async () => {
116 const response = await request(server)
117 .get('/users/1')
118 .expect(200);
119
120 expect(response.body).toMatchObject({
121 success: true,
122 data: {
123 id: 1,
124 name: 'John Doe',
125 email: 'john@example.com'
126 }
127 });
128 });
129
130 it('should return 404 for non-existent user', async () => {
131 const response = await request(server)
132 .get('/users/999')
133 .expect(404);
134
135 expect(response.body).toMatchObject({
136 success: false,
137 error: 'User not found'
138 });
139 });
140 });
141});