0
# Testing Utilities
1
2
Testing utilities for creating and testing Formly components and field configurations, providing helpers for unit and integration testing scenarios.
3
4
## Capabilities
5
6
### Component Factory Functions
7
8
Utility functions for creating test components and field components with proper setup and configuration.
9
10
```typescript { .api }
11
/**
12
* Create a test component with specified options
13
* @param options - Configuration options for the test component
14
* @returns FixtureUtils with testing helpers
15
*/
16
function createComponent<T>(options: IComponentOptions<T>): FixtureUtils;
17
18
/**
19
* Create a field component for testing with field configuration
20
* @param field - The field configuration to test
21
* @param config - Optional additional configuration
22
* @returns FixtureUtils with testing helpers and field-specific methods
23
*/
24
function createFieldComponent(field: FormlyFieldConfig, config?: any): FixtureUtils;
25
```
26
27
**Usage Examples:**
28
29
```typescript
30
import { createFieldComponent, createComponent } from '@ngx-formly/core/testing';
31
import { FormlyFieldConfig } from '@ngx-formly/core';
32
33
describe('Custom Field Component', () => {
34
let fixture: FixtureUtils;
35
36
beforeEach(() => {
37
const field: FormlyFieldConfig = {
38
key: 'testField',
39
type: 'input',
40
props: {
41
label: 'Test Field',
42
placeholder: 'Enter test value',
43
required: true
44
}
45
};
46
47
fixture = createFieldComponent(field);
48
});
49
50
it('should render field with correct label', () => {
51
expect(fixture.query('label').textContent).toContain('Test Field');
52
});
53
54
it('should mark field as required', () => {
55
const input = fixture.query('input');
56
expect(input.hasAttribute('required')).toBe(true);
57
});
58
59
it('should update model on input change', () => {
60
const input = fixture.query('input');
61
input.value = 'test value';
62
input.dispatchEvent(new Event('input'));
63
fixture.detectChanges();
64
65
expect(fixture.field.model.testField).toBe('test value');
66
});
67
});
68
```
69
70
### Custom Component Testing
71
72
```typescript
73
import { Component } from '@angular/core';
74
import { FormlyFieldConfig, FieldType } from '@ngx-formly/core';
75
import { createComponent } from '@ngx-formly/core/testing';
76
77
@Component({
78
selector: 'formly-field-custom-input',
79
template: `
80
<div class="custom-field">
81
<label [for]="id">{{ props.label }}</label>
82
<input
83
[id]="id"
84
[formControl]="formControl"
85
[placeholder]="props.placeholder"
86
[class.error]="showError"
87
(focus)="onFocus()"
88
(blur)="onBlur()">
89
<div *ngIf="showError" class="error-message">
90
{{ getErrorMessage() }}
91
</div>
92
</div>
93
`
94
})
95
export class CustomInputFieldComponent extends FieldType {
96
onFocus() {
97
console.log('Field focused');
98
}
99
100
onBlur() {
101
console.log('Field blurred');
102
}
103
104
getErrorMessage(): string {
105
if (this.formControl.errors?.['required']) {
106
return 'This field is required';
107
}
108
return '';
109
}
110
}
111
112
describe('CustomInputFieldComponent', () => {
113
let fixture: FixtureUtils;
114
115
beforeEach(() => {
116
fixture = createComponent({
117
component: CustomInputFieldComponent,
118
fieldConfig: {
119
key: 'customInput',
120
type: 'custom-input',
121
props: {
122
label: 'Custom Input',
123
placeholder: 'Enter value',
124
required: true
125
}
126
}
127
});
128
});
129
130
it('should display custom styling', () => {
131
const customField = fixture.query('.custom-field');
132
expect(customField).toBeTruthy();
133
});
134
135
it('should show error message when invalid', () => {
136
fixture.field.formControl.markAsTouched();
137
fixture.detectChanges();
138
139
const errorMessage = fixture.query('.error-message');
140
expect(errorMessage.textContent).toContain('This field is required');
141
});
142
143
it('should handle focus and blur events', () => {
144
spyOn(fixture.component, 'onFocus');
145
spyOn(fixture.component, 'onBlur');
146
147
const input = fixture.query('input');
148
input.dispatchEvent(new Event('focus'));
149
input.dispatchEvent(new Event('blur'));
150
151
expect(fixture.component.onFocus).toHaveBeenCalled();
152
expect(fixture.component.onBlur).toHaveBeenCalled();
153
});
154
});
155
```
156
157
## Types
158
159
### Testing Configuration Types
160
161
```typescript { .api }
162
interface IComponentOptions<T> {
163
/** The component class to test */
164
component: Type<T>;
165
166
/** Field configuration for the component */
167
fieldConfig?: FormlyFieldConfig;
168
169
/** Form model data */
170
model?: any;
171
172
/** Form options */
173
options?: FormlyFormOptions;
174
175
/** Additional providers for testing */
176
providers?: Provider[];
177
178
/** Additional imports for the test module */
179
imports?: any[];
180
181
/** Additional declarations for the test module */
182
declarations?: any[];
183
}
184
185
interface FixtureUtils {
186
/** Angular ComponentFixture instance */
187
fixture: ComponentFixture<any>;
188
189
/** Component instance */
190
component: any;
191
192
/** Field configuration used in the test */
193
field: FormlyFieldConfig;
194
195
/** Form model data */
196
model: any;
197
198
/** Form instance */
199
form: FormGroup;
200
201
/** Trigger change detection */
202
detectChanges(): void;
203
204
/** Query for DOM element by selector */
205
query<T extends Element = Element>(selector: string): T;
206
207
/** Query for all DOM elements by selector */
208
queryAll<T extends Element = Element>(selector: string): T[];
209
210
/** Set field value and trigger change detection */
211
setFieldValue(key: string, value: any): void;
212
213
/** Check if field has specific error */
214
hasFieldError(key: string, errorType: string): boolean;
215
216
/** Get field error message */
217
getFieldError(key: string): string | null;
218
}
219
```
220
221
### Mock Data Types
222
223
```typescript { .api }
224
/**
225
* Helper type for creating mock field configurations
226
*/
227
interface MockFieldConfig extends Partial<FormlyFieldConfig> {
228
key: string;
229
type: string;
230
}
231
232
/**
233
* Helper interface for mock form data
234
*/
235
interface MockFormData {
236
[key: string]: any;
237
}
238
239
/**
240
* Configuration for creating test scenarios
241
*/
242
interface TestScenario {
243
name: string;
244
fieldConfig: FormlyFieldConfig;
245
initialModel?: any;
246
expectedValue?: any;
247
actions?: Array<{
248
type: 'input' | 'select' | 'click' | 'focus' | 'blur';
249
selector: string;
250
value?: any;
251
}>;
252
}
253
```
254
255
## Import
256
257
```typescript
258
import {
259
createComponent,
260
createFieldComponent,
261
FixtureUtils,
262
IComponentOptions
263
} from '@ngx-formly/core/testing';
264
```
265
266
## Test Setup
267
268
### Basic Test Module Configuration
269
270
```typescript
271
import { TestBed } from '@angular/core/testing';
272
import { ReactiveFormsModule } from '@angular/forms';
273
import { FormlyModule } from '@ngx-formly/core';
274
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
275
276
beforeEach(async () => {
277
await TestBed.configureTestingModule({
278
imports: [
279
ReactiveFormsModule,
280
NoopAnimationsModule,
281
FormlyModule.forRoot({
282
types: [
283
{ name: 'input', component: FormlyFieldInput },
284
{ name: 'select', component: FormlyFieldSelect },
285
{ name: 'textarea', component: FormlyFieldTextarea }
286
]
287
})
288
]
289
}).compileComponents();
290
});
291
```
292
293
## Advanced Testing Examples
294
295
### Form Validation Testing
296
297
```typescript
298
describe('Form Validation', () => {
299
let fixture: FixtureUtils;
300
301
beforeEach(() => {
302
const fields: FormlyFieldConfig[] = [
303
{
304
key: 'email',
305
type: 'input',
306
props: {
307
label: 'Email',
308
type: 'email',
309
required: true
310
},
311
validators: {
312
email: {
313
validation: Validators.email,
314
message: 'Please enter a valid email address'
315
}
316
}
317
},
318
{
319
key: 'password',
320
type: 'input',
321
props: {
322
label: 'Password',
323
type: 'password',
324
required: true,
325
minLength: 8
326
}
327
}
328
];
329
330
fixture = createComponent({
331
fields,
332
model: {}
333
});
334
});
335
336
it('should show validation errors for invalid email', () => {
337
fixture.setFieldValue('email', 'invalid-email');
338
339
expect(fixture.hasFieldError('email', 'email')).toBe(true);
340
expect(fixture.getFieldError('email')).toContain('valid email address');
341
});
342
343
it('should validate password length', () => {
344
fixture.setFieldValue('password', '123');
345
346
expect(fixture.hasFieldError('password', 'minlength')).toBe(true);
347
});
348
349
it('should be valid with correct values', () => {
350
fixture.setFieldValue('email', 'user@example.com');
351
fixture.setFieldValue('password', 'securepassword123');
352
353
expect(fixture.form.valid).toBe(true);
354
});
355
});
356
```
357
358
### Dynamic Field Testing
359
360
```typescript
361
describe('Dynamic Fields', () => {
362
it('should show/hide fields based on conditions', () => {
363
const fields: FormlyFieldConfig[] = [
364
{
365
key: 'hasAccount',
366
type: 'checkbox',
367
props: {
368
label: 'I have an existing account'
369
}
370
},
371
{
372
key: 'username',
373
type: 'input',
374
props: {
375
label: 'Username'
376
},
377
expressions: {
378
hide: '!model.hasAccount'
379
}
380
}
381
];
382
383
const fixture = createComponent({ fields, model: {} });
384
385
// Initially username should be hidden
386
expect(fixture.query('input[id*="username"]')).toBeFalsy();
387
388
// Check the checkbox
389
fixture.setFieldValue('hasAccount', true);
390
fixture.detectChanges();
391
392
// Now username should be visible
393
expect(fixture.query('input[id*="username"]')).toBeTruthy();
394
});
395
});
396
```
397
398
### Custom Validator Testing
399
400
```typescript
401
function customValidator(control: AbstractControl): ValidationErrors | null {
402
if (control.value && control.value.length < 5) {
403
return { tooShort: { actualLength: control.value.length, requiredLength: 5 } };
404
}
405
return null;
406
}
407
408
describe('Custom Validator', () => {
409
it('should validate with custom validator', () => {
410
const field: FormlyFieldConfig = {
411
key: 'customField',
412
type: 'input',
413
validators: {
414
custom: {
415
validation: customValidator,
416
message: 'Value must be at least 5 characters long'
417
}
418
}
419
};
420
421
const fixture = createFieldComponent(field);
422
423
fixture.setFieldValue('customField', '123');
424
425
expect(fixture.hasFieldError('customField', 'tooShort')).toBe(true);
426
expect(fixture.getFieldError('customField')).toContain('5 characters long');
427
});
428
});
429
```