Testing Guide
Testing strategies for MoroJS applications using standard testing libraries. Learn unit testing, integration testing, and API testing patterns.
What You'll Learn
Unit Testing
Test individual services and functions in isolation
Integration Testing
Test API endpoints and request/response flows
Test Setup
Configure Jest and testing utilities
Mocking
Create mocks for databases and external services
Progress0 / 4 steps
Testing Setup
Set up Jest and testing utilities for your MoroJS application.
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
Test individual services and functions in isolation with mocks.
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
Test API endpoints and request/response flows with Supertest.
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});Running Tests
Run your tests and view coverage reports.
Test Commands
typescript
1# Run all tests
2npm test
3
4# Run tests in watch mode
5npm run test:watch
6
7# Run tests with coverage
8npm test -- --coverage
9
10# Run specific test file
11npm test -- UserService.test.ts
12
13# Run tests matching a pattern
14npm test -- --testNamePattern="createUser"