integration-testing-patterns.md
Comprehensive integration testing patterns for micro-block architecture with real service validation
integration-testing-patterns.mdv1.0.014.9 KB
integration-testing-patterns.md(markdown)
1# Integration Testing Patterns for Micro-Block Architecture
2
3## Overview
4
5Integration testing is the primary validation method for micro-block commands. Unlike unit tests that mock dependencies, integration tests connect to real services and test actual functionality end-to-end.
6
7## Integration Testing Philosophy
8
9**Integration tests are preferred over unit tests** for validating command implementations because:
10
111. **Real Service Validation**: Tests execute against actual services, ensuring they work correctly
122. **Full System Testing**: All dependencies work together as they would in production
133. **Data Integrity Verification**: Tests verify that actual database schema, constraints, and relationships work
144. **Environment Configuration Testing**: Tests validate that connection strings, configuration, and service setup work properly
155. **Contract Compliance**: Tests verify that services actually implement their declared interfaces
16
17## Core Testing Patterns
18
19### Test Structure Pattern
20
21```typescript
22describe('Command Integration Tests', () => {
23 let serviceRegistry: ServiceRegistry;
24 let commandRegistry: CommandRegistry;
25 let logger: ConsoleLogger;
26
27 beforeAll(async () => {
28 // Load real environment configuration
29 dotenv.config({ path: path.resolve(process.cwd(), '.env.local') });
30
31 // Initialize ServiceRegistry with real configuration
32 serviceRegistry = ServiceRegistry.getInstance();
33 await serviceRegistry.initialize();
34
35 // Get CommandRegistry through ServiceRegistry
36 commandRegistry = serviceRegistry.getCommandRegistry();
37
38 // Create logger for test output
39 logger = new ConsoleLogger('IntegrationTest');
40
41 // Verify critical services are healthy
42 const databaseService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
43 const isHealthy = await databaseService.isHealthy();
44 if (!isHealthy) {
45 throw new Error('Database service is not healthy - cannot run integration tests');
46 }
47 });
48
49 afterAll(async () => {
50 // Cleanup resources
51 await serviceRegistry.destroy();
52 });
53});
54```
55
56### Command Testing Pattern
57
58```typescript
59describe('CreateUserCommand Integration', () => {
60 it('should create user with real database', async () => {
61 // 1. Get command through CommandRegistry (not direct instantiation)
62 const { CreateUserCommand } = await import('@/commands/user/CreateUserCommand');
63 const command = await commandRegistry.get(CreateUserCommand, {
64 email: 'integration-test@example.com',
65 password: 'password123',
66 name: 'Integration Test User'
67 }, logger);
68
69 // 2. Execute against real services
70 const result = await command.execute();
71
72 // 3. Verify results with real data
73 expect(result.userId).toBeDefined();
74 expect(result.email).toBe('integration-test@example.com');
75 expect(result.createdAt).toBeInstanceOf(Date);
76
77 // 4. Verify in database (optional verification)
78 const databaseService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
79 const users = await databaseService.query(
80 'SELECT * FROM Users WHERE email = @email',
81 { email: 'integration-test@example.com' }
82 );
83 expect(users).toHaveLength(1);
84
85 // 5. Cleanup test data
86 await databaseService.execute(
87 'DELETE FROM Users WHERE email = @email',
88 { email: 'integration-test@example.com' }
89 );
90 });
91
92 it('should handle validation errors appropriately', async () => {
93 const { CreateUserCommand } = await import('@/commands/user/CreateUserCommand');
94
95 // Test with invalid input
96 await expect(async () => {
97 const command = await commandRegistry.get(CreateUserCommand, {
98 email: 'invalid-email', // Invalid format
99 password: '123', // Too short
100 name: '' // Empty name
101 }, logger);
102
103 await command.execute();
104 }).rejects.toThrow('Email must be a valid email address format');
105 });
106});
107```
108
109### Service Testing Pattern
110
111```typescript
112describe('DatabaseService Integration', () => {
113 let databaseService: IDatabaseService;
114
115 beforeEach(() => {
116 databaseService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
117 });
118
119 it('should implement IDatabaseService contract', () => {
120 expect(databaseService).toBeDefined();
121 expect(typeof databaseService.query).toBe('function');
122 expect(typeof databaseService.execute).toBe('function');
123 expect(typeof databaseService.isHealthy).toBe('function');
124 });
125
126 it('should connect to real database', async () => {
127 const isHealthy = await databaseService.isHealthy();
128 expect(isHealthy).toBe(true);
129 });
130
131 it('should execute queries against real database', async () => {
132 const result = await databaseService.query('SELECT 1 as test');
133 expect(result).toHaveLength(1);
134 expect(result[0].test).toBe(1);
135 });
136
137 it('should handle transactions properly', async () => {
138 await databaseService.transaction(async (tx) => {
139 await tx.execute('INSERT INTO TestTable (name) VALUES (@name)', { name: 'test' });
140 const rows = await tx.query('SELECT * FROM TestTable WHERE name = @name', { name: 'test' });
141 expect(rows).toHaveLength(1);
142
143 // Transaction will rollback automatically for test cleanup
144 throw new Error('Rollback test transaction');
145 }).catch(error => {
146 expect(error.message).toBe('Rollback test transaction');
147 });
148 });
149});
150```
151
152### Workflow Testing Pattern
153
154```typescript
155describe('User Creation Workflow Integration', () => {
156 it('should execute complete user creation workflow', async () => {
157 // Step 1: Validate user data
158 const { ValidateUserDataCommand } = await import('@/commands/user/ValidateUserDataCommand');
159 const validateCommand = await commandRegistry.get(ValidateUserDataCommand, {
160 email: 'workflow-test@example.com',
161 password: 'securePassword123'
162 }, logger);
163
164 const validationResult = await validateCommand.execute();
165 expect(validationResult.isValid).toBe(true);
166
167 // Step 2: Create user account
168 const { CreateUserCommand } = await import('@/commands/user/CreateUserCommand');
169 const createCommand = await commandRegistry.get(CreateUserCommand, {
170 email: 'workflow-test@example.com',
171 password: 'securePassword123',
172 name: 'Workflow Test User'
173 }, logger);
174
175 const createResult = await createCommand.execute();
176 expect(createResult.userId).toBeDefined();
177
178 // Step 3: Send welcome email
179 const { SendWelcomeEmailCommand } = await import('@/commands/email/SendWelcomeEmailCommand');
180 const emailCommand = await commandRegistry.get(SendWelcomeEmailCommand, {
181 userId: createResult.userId,
182 email: createResult.email,
183 name: 'Workflow Test User'
184 }, logger);
185
186 const emailResult = await emailCommand.execute();
187 expect(emailResult.emailSent).toBe(true);
188
189 // Cleanup
190 const databaseService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
191 await databaseService.execute(
192 'DELETE FROM Users WHERE email = @email',
193 { email: 'workflow-test@example.com' }
194 );
195 });
196});
197```
198
199## Testing Service Implementations
200
201### Multiple Implementation Testing
202
203```typescript
204describe('Cache Service Implementations', () => {
205 const testData = { key: 'test-key', value: { data: 'test-value', timestamp: Date.now() } };
206
207 // Test all cache implementations
208 const implementations = ['MemoryCacheService', 'RedisCacheService'];
209
210 implementations.forEach(implementationName => {
211 describe(`${implementationName} Integration`, () => {
212 let cacheService: ICacheService;
213
214 beforeEach(() => {
215 // Get the specific implementation from registry
216 cacheService = serviceRegistry.get<ICacheService>(`I${implementationName}`);
217 });
218
219 afterEach(async () => {
220 // Clean up test data
221 await cacheService.flush();
222 });
223
224 it('should implement ICacheService contract', () => {
225 expect(cacheService).toBeDefined();
226 expect(typeof cacheService.get).toBe('function');
227 expect(typeof cacheService.set).toBe('function');
228 expect(typeof cacheService.delete).toBe('function');
229 expect(typeof cacheService.flush).toBe('function');
230 });
231
232 it('should store and retrieve data', async () => {
233 await cacheService.set(testData.key, testData.value);
234 const retrieved = await cacheService.get(testData.key);
235
236 expect(retrieved).toEqual(testData.value);
237 });
238
239 it('should handle TTL expiration', async () => {
240 await cacheService.set(testData.key, testData.value, 1); // 1 second TTL
241
242 // Immediate retrieval should work
243 let retrieved = await cacheService.get(testData.key);
244 expect(retrieved).toEqual(testData.value);
245
246 // Wait for expiration
247 await new Promise(resolve => setTimeout(resolve, 1100));
248
249 // Should be expired
250 retrieved = await cacheService.get(testData.key);
251 expect(retrieved).toBeNull();
252 });
253 });
254 });
255});
256```
257
258## Error Handling Testing
259
260### Error Contract Testing
261
262```typescript
263describe('Command Error Handling Integration', () => {
264 it('should throw properly structured errors', async () => {
265 const { CreateUserCommand } = await import('@/commands/user/CreateUserCommand');
266
267 try {
268 const command = await commandRegistry.get(CreateUserCommand, {
269 email: 'invalid-email',
270 password: 'short',
271 name: ''
272 }, logger);
273
274 await command.execute();
275 fail('Command should have thrown validation error');
276 } catch (error) {
277 expect(error).toBeInstanceOf(Error);
278 expect(error.name).toBe('CreateUserError');
279 expect(error.code).toBe('VALIDATION_ERROR');
280 expect(error.invalidInput).toBe(true);
281 expect(error.invalidInputName).toBeDefined();
282 expect(error.invalidInputPath).toBeDefined();
283 expect(error.timestamp).toBeInstanceOf(Date);
284 }
285 });
286
287 it('should handle service unavailability gracefully', async () => {
288 // Temporarily make database unhealthy
289 const databaseService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
290 const originalIsHealthy = databaseService.isHealthy;
291 databaseService.isHealthy = async () => false;
292
293 try {
294 const { CreateUserCommand } = await import('@/commands/user/CreateUserCommand');
295 const command = await commandRegistry.get(CreateUserCommand, {
296 email: 'test@example.com',
297 password: 'password123',
298 name: 'Test User'
299 }, logger);
300
301 await command.execute();
302 fail('Command should have thrown service error');
303 } catch (error) {
304 expect(error.code).toBe('SERVICE_UNHEALTHY');
305 } finally {
306 // Restore original method
307 databaseService.isHealthy = originalIsHealthy;
308 }
309 });
310});
311```
312
313## Performance Testing
314
315### Performance Benchmarking
316
317```typescript
318describe('Command Performance Integration', () => {
319 it('should execute within acceptable time limits', async () => {
320 const { GetUserCommand } = await import('@/commands/user/GetUserCommand');
321
322 const startTime = Date.now();
323
324 const command = await commandRegistry.get(GetUserCommand, {
325 userId: 'existing-user-id'
326 }, logger);
327
328 const result = await command.execute();
329
330 const executionTime = Date.now() - startTime;
331
332 expect(result).toBeDefined();
333 expect(executionTime).toBeLessThan(1000); // Should complete within 1 second
334
335 logger.info(`GetUserCommand executed in ${executionTime}ms`);
336 });
337
338 it('should handle concurrent executions', async () => {
339 const { GetUserCommand } = await import('@/commands/user/GetUserCommand');
340 const userIds = ['user1', 'user2', 'user3', 'user4', 'user5'];
341
342 const startTime = Date.now();
343
344 // Execute commands concurrently
345 const promises = userIds.map(async (userId) => {
346 const command = await commandRegistry.get(GetUserCommand, { userId }, logger);
347 return command.execute();
348 });
349
350 const results = await Promise.all(promises);
351
352 const totalTime = Date.now() - startTime;
353
354 expect(results).toHaveLength(5);
355 expect(totalTime).toBeLessThan(2000); // Concurrent execution should be faster than sequential
356
357 logger.info(`Concurrent execution completed in ${totalTime}ms`);
358 });
359});
360```
361
362## Test Data Management
363
364### Test Data Setup Pattern
365
366```typescript
367describe('Integration Test with Test Data', () => {
368 let testUserId: string;
369 let testArtifactId: string;
370
371 beforeEach(async () => {
372 // Setup test data
373 const { CreateUserCommand } = await import('@/commands/user/CreateUserCommand');
374 const createUserCommand = await commandRegistry.get(CreateUserCommand, {
375 email: `test-${Date.now()}@example.com`,
376 password: 'password123',
377 name: 'Test User'
378 }, logger);
379
380 const userResult = await createUserCommand.execute();
381 testUserId = userResult.userId;
382
383 const { CreateArtifactCommand } = await import('@/commands/artifact/CreateArtifactCommand');
384 const createArtifactCommand = await commandRegistry.get(CreateArtifactCommand, {
385 userId: testUserId,
386 name: 'Test Artifact',
387 content: 'Test content'
388 }, logger);
389
390 const artifactResult = await createArtifactCommand.execute();
391 testArtifactId = artifactResult.artifactId;
392 });
393
394 afterEach(async () => {
395 // Cleanup test data
396 const databaseService = serviceRegistry.get<IDatabaseService>('IDatabaseService');
397
398 await databaseService.execute(
399 'DELETE FROM Artifacts WHERE id = @artifactId',
400 { artifactId: testArtifactId }
401 );
402
403 await databaseService.execute(
404 'DELETE FROM Users WHERE id = @userId',
405 { userId: testUserId }
406 );
407 });
408
409 it('should test with fresh test data', async () => {
410 // Test implementation using testUserId and testArtifactId
411 });
412});
413```
414
415## Best Practices
416
417### Integration Test Design
418
4191. **Test Real Integrations**: Use actual services, not mocks
4202. **Test Complete Workflows**: Verify end-to-end functionality
4213. **Verify Error Handling**: Test error scenarios with real services
4224. **Performance Awareness**: Monitor execution times
4235. **Data Cleanup**: Always clean up test data
424
425### Test Organization
426
4271. **Separate by Category**: Group tests by command category
4282. **Service Contract Testing**: Verify service implementations
4293. **Workflow Testing**: Test command compositions
4304. **Error Scenario Coverage**: Test all error paths
4315. **Performance Benchmarking**: Include performance tests
432
433### Environment Setup
434
4351. **Real Configuration**: Use actual environment variables
4362. **Service Health Checks**: Verify services before testing
4373. **Database Transactions**: Use transactions for data isolation
4384. **Resource Cleanup**: Properly cleanup resources after tests
4395. **Logging**: Include detailed logging for debugging
440
441This integration testing approach ensures that micro-block commands work correctly with real services and data, providing confidence in the system's reliability and performance.
Metadata
- Path
- utaba/main/patterns/testing/integration-testing-patterns.md
- Namespace
- utaba/main/patterns/testing
- Author
- utaba
- Category
- patterns
- Technology
- testing
- Contract Version
- 1.0.0
- MIME Type
- text/markdown
- Published
- 18-Jul-2025
- Last Updated
- 18-Jul-2025