Skip to content

Commit 7916435

Browse files
Copilotrenemadsen
andcommitted
Add comprehensive testing documentation and additional spec files for folders and workers components
Co-authored-by: renemadsen <[email protected]>
1 parent 44d2a25 commit 7916435

File tree

4 files changed

+776
-0
lines changed

4 files changed

+776
-0
lines changed

eform-client/TESTING.md

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# Karma Unit Testing Guide for eForm Angular Frontend
2+
3+
## Overview
4+
5+
This document describes the unit testing approach and patterns used for testing Angular components and services in the eform-client/src/app/modules directory.
6+
7+
## Test Infrastructure
8+
9+
### Tools & Frameworks
10+
- **Jasmine**: Behavior-driven testing framework
11+
- **Karma**: Test runner for executing tests in browsers
12+
- **Angular Testing Utilities**: `TestBed`, `ComponentFixture`, etc.
13+
14+
### Configuration Files
15+
- `src/karma.conf.js`: Karma configuration with increased timeouts for CI/CD
16+
- `src/test.ts`: Test entry point with zone.js and Jasmine setup
17+
- `src/tsconfig.spec.json`: TypeScript configuration for tests
18+
19+
### Running Tests
20+
21+
```bash
22+
# Run all tests
23+
ng test
24+
25+
# Run tests in headless mode (for CI/CD)
26+
ng test --watch=false --browsers=ChromeHeadless
27+
28+
# Run with code coverage
29+
ng test --code-coverage
30+
```
31+
32+
## Testing Patterns and Best Practices
33+
34+
### 1. Component Testing Structure
35+
36+
Every component test file should follow this structure:
37+
38+
```typescript
39+
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
40+
import { ComponentName } from './component-name.component';
41+
// Import dependencies...
42+
43+
describe('ComponentName', () => {
44+
let component: ComponentName;
45+
let fixture: ComponentFixture<ComponentName>;
46+
let mockService: jasmine.SpyObj<ServiceName>;
47+
48+
beforeEach(waitForAsync(() => {
49+
// Create spy objects for dependencies
50+
mockService = jasmine.createSpyObj('ServiceName', ['method1', 'method2']);
51+
52+
TestBed.configureTestingModule({
53+
declarations: [ComponentName],
54+
providers: [
55+
{ provide: ServiceName, useValue: mockService },
56+
// ... other providers
57+
]
58+
}).compileComponents();
59+
}));
60+
61+
beforeEach(() => {
62+
fixture = TestBed.createComponent(ComponentName);
63+
component = fixture.componentInstance;
64+
});
65+
66+
it('should create', () => {
67+
expect(component).toBeTruthy();
68+
});
69+
70+
// Additional tests...
71+
});
72+
```
73+
74+
### 2. Mocking Services
75+
76+
Always mock external dependencies using Jasmine spies:
77+
78+
```typescript
79+
// Create a spy object
80+
mockService = jasmine.createSpyObj('ServiceName', ['getAllItems', 'createItem']);
81+
82+
// Configure return values
83+
mockService.getAllItems.and.returnValue(of({
84+
success: true,
85+
message: '',
86+
model: []
87+
}));
88+
89+
// Verify method calls
90+
expect(mockService.getAllItems).toHaveBeenCalled();
91+
expect(mockService.createItem).toHaveBeenCalledWith(expectedData);
92+
```
93+
94+
### 3. Mocking Angular Material Dialog
95+
96+
```typescript
97+
let mockDialog: jasmine.SpyObj<MatDialog>;
98+
99+
beforeEach(() => {
100+
mockDialog = jasmine.createSpyObj('MatDialog', ['open']);
101+
});
102+
103+
// In tests
104+
const mockDialogRef = {
105+
afterClosed: () => of(true) // or of(false) for cancellation
106+
};
107+
mockDialog.open.and.returnValue(mockDialogRef as any);
108+
```
109+
110+
### 4. Mocking NgRx Store
111+
112+
```typescript
113+
let mockStore: jasmine.SpyObj<Store>;
114+
115+
beforeEach(() => {
116+
mockStore = jasmine.createSpyObj('Store', ['select', 'dispatch']);
117+
mockStore.select.and.returnValue(of(true)); // or appropriate selector value
118+
});
119+
```
120+
121+
### 5. Mocking TranslateService
122+
123+
```typescript
124+
let mockTranslateService: jasmine.SpyObj<TranslateService>;
125+
126+
beforeEach(() => {
127+
mockTranslateService = jasmine.createSpyObj('TranslateService', ['stream', 'instant']);
128+
mockTranslateService.stream.and.returnValue(of('Translated Text'));
129+
});
130+
```
131+
132+
### 6. Testing Observable-Based Methods
133+
134+
```typescript
135+
describe('loadAllItems', () => {
136+
it('should load items successfully', () => {
137+
const mockItems: ItemDto[] = [
138+
{ id: 1, name: 'Item 1' } as ItemDto,
139+
{ id: 2, name: 'Item 2' } as ItemDto
140+
];
141+
const mockResult: OperationDataResult<Array<ItemDto>> = {
142+
success: true,
143+
message: '',
144+
model: mockItems
145+
};
146+
mockService.getAllItems.and.returnValue(of(mockResult));
147+
148+
component.loadAllItems();
149+
150+
expect(mockService.getAllItems).toHaveBeenCalled();
151+
expect(component.items).toEqual(mockItems);
152+
});
153+
154+
it('should handle unsuccessful response', () => {
155+
const mockResult: OperationDataResult<Array<ItemDto>> = {
156+
success: false,
157+
message: 'Error message',
158+
model: null
159+
};
160+
mockService.getAllItems.and.returnValue(of(mockResult));
161+
162+
component.loadAllItems();
163+
164+
expect(mockService.getAllItems).toHaveBeenCalled();
165+
expect(component.items).toEqual([]);
166+
});
167+
});
168+
```
169+
170+
### 7. Testing Dialog Interactions
171+
172+
```typescript
173+
describe('openCreateModal', () => {
174+
it('should open modal and reload data on success', () => {
175+
const mockDialogRef = {
176+
afterClosed: () => of(true)
177+
};
178+
mockDialog.open.and.returnValue(mockDialogRef as any);
179+
180+
spyOn(component, 'loadAllItems');
181+
component.openCreateModal();
182+
183+
expect(mockDialog.open).toHaveBeenCalled();
184+
expect(component.loadAllItems).toHaveBeenCalled();
185+
});
186+
187+
it('should not reload data when modal is cancelled', () => {
188+
const mockDialogRef = {
189+
afterClosed: () => of(false)
190+
};
191+
mockDialog.open.and.returnValue(mockDialogRef as any);
192+
193+
spyOn(component, 'loadAllItems');
194+
component.openCreateModal();
195+
196+
expect(mockDialog.open).toHaveBeenCalled();
197+
expect(component.loadAllItems).not.toHaveBeenCalled();
198+
});
199+
});
200+
```
201+
202+
### 8. Testing MatDialogRef Close
203+
204+
For components that use MatDialogRef:
205+
206+
```typescript
207+
let mockDialogRef: jasmine.SpyObj<MatDialogRef<ComponentName>>;
208+
209+
beforeEach(() => {
210+
mockDialogRef = jasmine.createSpyObj('MatDialogRef', ['close']);
211+
});
212+
213+
it('should close dialog with true when successful', () => {
214+
component.hide(true);
215+
expect(mockDialogRef.close).toHaveBeenCalledWith(true);
216+
});
217+
218+
it('should close dialog with false by default', () => {
219+
component.hide();
220+
expect(mockDialogRef.close).toHaveBeenCalledWith(false);
221+
});
222+
```
223+
224+
### 9. Testing MAT_DIALOG_DATA Injection
225+
226+
```typescript
227+
let mockDialogData: DataModel;
228+
229+
beforeEach(() => {
230+
mockDialogData = { id: 1, name: 'Test' } as DataModel;
231+
232+
TestBed.configureTestingModule({
233+
declarations: [ComponentName],
234+
providers: [
235+
{ provide: MAT_DIALOG_DATA, useValue: mockDialogData },
236+
// ... other providers
237+
]
238+
});
239+
});
240+
241+
it('should initialize with dialog data', () => {
242+
expect(component.data).toBeDefined();
243+
expect(component.data.id).toBe(1);
244+
});
245+
```
246+
247+
## Common Data Models
248+
249+
### OperationResult and OperationDataResult
250+
251+
All API responses use these models, which must include the `message` property:
252+
253+
```typescript
254+
// OperationResult
255+
const mockResult: OperationResult = {
256+
success: true,
257+
message: ''
258+
};
259+
260+
// OperationDataResult
261+
const mockResult: OperationDataResult<T> = {
262+
success: true,
263+
message: '',
264+
model: data
265+
};
266+
```
267+
268+
## Example Test Files
269+
270+
Reference implementations can be found in:
271+
- `src/app/modules/advanced/components/units/units.component.spec.ts`
272+
- `src/app/modules/advanced/components/units/unit-create/unit-create.component.spec.ts`
273+
- `src/app/modules/advanced/components/units/units-otp-code/units-otp-code.component.spec.ts`
274+
- `src/app/modules/advanced/components/workers/workers/workers.component.spec.ts`
275+
- `src/app/modules/advanced/components/workers/worker-edit-create/worker-edit-create.component.spec.ts`
276+
- `src/app/modules/advanced/components/folders/folders/folders.component.spec.ts`
277+
278+
## Coverage Goals
279+
280+
- **Methods**: All public methods should have test coverage
281+
- **Scenarios**: Test both success and failure cases
282+
- **Edge Cases**: Test null/undefined values, empty arrays, etc.
283+
- **Interactions**: Test component interactions with services and dialogs
284+
285+
## Test Naming Conventions
286+
287+
- Use descriptive test names: `'should load items successfully'`
288+
- Group related tests using `describe` blocks
289+
- Start with `'should'` for behavior descriptions
290+
- Be specific about what is being tested
291+
292+
## Troubleshooting
293+
294+
### Common Issues
295+
296+
1. **Missing message property**: Ensure all OperationResult/OperationDataResult objects include `message: ''`
297+
2. **Zone.js imports**: Use `import 'zone.js'` and `import 'zone.js/testing'` in test.ts
298+
3. **Karma timeouts**: Increase `browserNoActivityTimeout` in karma.conf.js for slow tests
299+
4. **Spy not configured**: Always configure spy return values with `.and.returnValue()`
300+
301+
### Debugging Tests
302+
303+
```bash
304+
# Run tests with source maps for debugging
305+
ng test --source-map
306+
307+
# Run a single test file (may not work with all configurations)
308+
ng test --include='**/component-name.component.spec.ts'
309+
310+
# Increase Karma logging
311+
# Edit karma.conf.js and set logLevel: config.LOG_DEBUG
312+
```
313+
314+
## Continuous Integration
315+
316+
Tests are designed to run in CI/CD pipelines:
317+
318+
```bash
319+
# Headless mode for CI/CD
320+
ng test --watch=false --browsers=ChromeHeadless --code-coverage
321+
```
322+
323+
## Contributing
324+
325+
When adding new components:
326+
1. Create a `.spec.ts` file alongside the component
327+
2. Follow the patterns documented here
328+
3. Ensure all public methods are tested
329+
4. Test both success and error scenarios
330+
5. Run tests locally before committing
331+
332+
## Additional Resources
333+
334+
- [Angular Testing Guide](https://angular.dev/guide/testing)
335+
- [Jasmine Documentation](https://jasmine.github.io/)
336+
- [Karma Configuration](https://karma-runner.github.io/latest/config/configuration-file.html)

0 commit comments

Comments
 (0)