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});

Next Steps