0
# Testing Utilities
1
2
Testing utilities provide comprehensive mock implementations and testing helpers for NgRx Store, enabling effective unit testing of components, services, and state management logic.
3
4
## Capabilities
5
6
### Provide Mock Store
7
8
Creates mock store providers for testing with TestBed configuration.
9
10
```typescript { .api }
11
/**
12
* Creates mock store providers for testing environments
13
* @param config - Mock store configuration with initial state and selectors
14
* @returns Provider array for TestBed configuration
15
*/
16
function provideMockStore<T = any>(
17
config?: MockStoreConfig<T>
18
): (ValueProvider | ExistingProvider | FactoryProvider)[];
19
20
/**
21
* Configuration for mock store setup
22
*/
23
interface MockStoreConfig<T> {
24
/** Initial state for mock store */
25
initialState?: T;
26
/** Mock selectors with predefined return values */
27
selectors?: MockSelector[];
28
}
29
```
30
31
**Usage Examples:**
32
33
```typescript
34
import { TestBed } from "@angular/core/testing";
35
import { provideMockStore, MockStore } from "@ngrx/store/testing";
36
import { selectUser, selectUserName } from "./user.selectors";
37
38
describe('UserComponent', () => {
39
let store: MockStore;
40
let component: UserComponent;
41
let fixture: ComponentFixture<UserComponent>;
42
43
beforeEach(async () => {
44
await TestBed.configureTestingModule({
45
imports: [UserComponent],
46
providers: [
47
provideMockStore({
48
initialState: {
49
user: {
50
entities: [
51
{ id: '1', name: 'John Doe', email: 'john@example.com' },
52
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' }
53
],
54
selectedUserId: '1',
55
loading: false,
56
error: null
57
}
58
},
59
selectors: [
60
{ selector: selectUser, value: { id: '1', name: 'John Doe' } },
61
{ selector: selectUserName, value: 'John Doe' }
62
]
63
})
64
]
65
}).compileComponents();
66
67
store = TestBed.inject(MockStore);
68
fixture = TestBed.createComponent(UserComponent);
69
component = fixture.componentInstance;
70
});
71
72
it('should display user name', () => {
73
fixture.detectChanges();
74
expect(component.userName()).toBe('John Doe');
75
});
76
});
77
```
78
79
### Create Mock Store
80
81
Creates a standalone mock store instance without TestBed for service and effect testing.
82
83
```typescript { .api }
84
/**
85
* Creates mock store instance outside of TestBed
86
* @param config - Mock store configuration
87
* @returns MockStore instance for testing
88
*/
89
function createMockStore<T>(config?: MockStoreConfig<T>): MockStore<T>;
90
```
91
92
**Usage Examples:**
93
94
```typescript
95
import { createMockStore } from "@ngrx/store/testing";
96
import { UserEffects } from "./user.effects";
97
98
describe('UserEffects', () => {
99
let effects: UserEffects;
100
let store: MockStore;
101
let userService: jasmine.SpyObj<UserService>;
102
103
beforeEach(() => {
104
const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser', 'updateUser']);
105
106
store = createMockStore({
107
initialState: {
108
user: { entities: [], loading: false, error: null }
109
}
110
});
111
112
TestBed.configureTestingModule({
113
providers: [
114
UserEffects,
115
{ provide: Store, useValue: store },
116
{ provide: UserService, useValue: userServiceSpy }
117
]
118
});
119
120
effects = TestBed.inject(UserEffects);
121
userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
122
});
123
124
it('should load user data', () => {
125
userService.loadUser.and.returnValue(of({ id: '1', name: 'Test User' }));
126
127
store.dispatch(loadUser({ id: '1' }));
128
129
expect(userService.loadUser).toHaveBeenCalledWith('1');
130
});
131
});
132
```
133
134
### Mock Store Class
135
136
Enhanced Store implementation with testing-specific methods for overriding selectors and managing state.
137
138
```typescript { .api }
139
/**
140
* Mock implementation of Store for testing
141
*/
142
class MockStore<T> extends Store<T> {
143
/**
144
* Override selector return value for testing
145
* @param selector - Selector to override
146
* @param value - Value to return from selector
147
*/
148
overrideSelector<Result>(
149
selector: MemoizedSelector<any, Result>,
150
value: Result
151
): void;
152
153
/**
154
* Refresh mock state and notify subscribers
155
*/
156
refreshState(): void;
157
158
/**
159
* Set mock state directly
160
* @param nextState - New state to set
161
*/
162
setState(nextState: T): void;
163
164
/**
165
* Reset all selector overrides
166
*/
167
resetSelectors(): void;
168
}
169
```
170
171
**Usage Examples:**
172
173
```typescript
174
import { MockStore } from "@ngrx/store/testing";
175
import { selectUserPreferences, selectTheme } from "./ui.selectors";
176
177
describe('UI Component Tests', () => {
178
let store: MockStore;
179
let component: ThemeToggleComponent;
180
181
beforeEach(() => {
182
TestBed.configureTestingModule({
183
providers: [provideMockStore()]
184
});
185
186
store = TestBed.inject(MockStore);
187
component = TestBed.inject(ThemeToggleComponent);
188
});
189
190
it('should handle theme changes', () => {
191
// Override selector return value
192
store.overrideSelector(selectTheme, 'dark');
193
store.refreshState();
194
195
expect(component.currentTheme()).toBe('dark');
196
197
// Change selector value during test
198
store.overrideSelector(selectTheme, 'light');
199
store.refreshState();
200
201
expect(component.currentTheme()).toBe('light');
202
});
203
204
it('should handle complex state changes', () => {
205
const mockPreferences = {
206
theme: 'auto',
207
language: 'en',
208
notifications: true
209
};
210
211
store.overrideSelector(selectUserPreferences, mockPreferences);
212
store.refreshState();
213
214
expect(component.preferences()).toEqual(mockPreferences);
215
216
// Direct state manipulation
217
store.setState({
218
...store.state(),
219
ui: {
220
preferences: { ...mockPreferences, theme: 'dark' }
221
}
222
});
223
224
expect(component.preferences().theme).toBe('dark');
225
});
226
});
227
```
228
229
### Mock Selector Interface
230
231
Configuration for mocking individual selectors with predefined return values.
232
233
```typescript { .api }
234
/**
235
* Configuration for mocking individual selectors
236
*/
237
interface MockSelector {
238
/** The selector to mock */
239
selector: MemoizedSelector<any, any> | string;
240
/** The value to return from the selector */
241
value: any;
242
}
243
```
244
245
**Usage Examples:**
246
247
```typescript
248
import { selectAllUsers, selectActiveUsers, selectUserCount } from "./user.selectors";
249
250
const mockSelectors: MockSelector[] = [
251
{
252
selector: selectAllUsers,
253
value: [
254
{ id: '1', name: 'John', active: true },
255
{ id: '2', name: 'Jane', active: false }
256
]
257
},
258
{
259
selector: selectActiveUsers,
260
value: [{ id: '1', name: 'John', active: true }]
261
},
262
{
263
selector: selectUserCount,
264
value: 2
265
}
266
];
267
268
TestBed.configureTestingModule({
269
providers: [
270
provideMockStore({
271
selectors: mockSelectors
272
})
273
]
274
});
275
```
276
277
## Testing Patterns
278
279
### Component Testing
280
281
```typescript
282
import { ComponentFixture, TestBed } from "@angular/core/testing";
283
import { MockStore } from "@ngrx/store/testing";
284
import { UserListComponent } from "./user-list.component";
285
286
describe('UserListComponent', () => {
287
let component: UserListComponent;
288
let fixture: ComponentFixture<UserListComponent>;
289
let store: MockStore;
290
291
const initialState = {
292
users: {
293
entities: [],
294
loading: false,
295
error: null,
296
filters: { search: '', role: 'all' }
297
}
298
};
299
300
beforeEach(async () => {
301
await TestBed.configureTestingModule({
302
imports: [UserListComponent],
303
providers: [provideMockStore({ initialState })]
304
}).compileComponents();
305
306
store = TestBed.inject(MockStore);
307
fixture = TestBed.createComponent(UserListComponent);
308
component = fixture.componentInstance;
309
});
310
311
it('should display loading state', () => {
312
store.setState({
313
...initialState,
314
users: { ...initialState.users, loading: true }
315
});
316
317
fixture.detectChanges();
318
319
expect(fixture.nativeElement.querySelector('.loading')).toBeTruthy();
320
});
321
322
it('should dispatch search action', () => {
323
spyOn(store, 'dispatch');
324
325
component.searchUsers('john');
326
327
expect(store.dispatch).toHaveBeenCalledWith(
328
searchUsers({ query: 'john' })
329
);
330
});
331
332
it('should handle error state', () => {
333
const errorMessage = 'Failed to load users';
334
335
store.setState({
336
...initialState,
337
users: { ...initialState.users, error: errorMessage }
338
});
339
340
fixture.detectChanges();
341
342
expect(fixture.nativeElement.textContent).toContain(errorMessage);
343
});
344
});
345
```
346
347
### Service Testing
348
349
```typescript
350
import { TestBed } from "@angular/core/testing";
351
import { createMockStore, MockStore } from "@ngrx/store/testing";
352
import { UserDataService } from "./user-data.service";
353
354
describe('UserDataService', () => {
355
let service: UserDataService;
356
let store: MockStore;
357
358
beforeEach(() => {
359
store = createMockStore({
360
initialState: {
361
users: { entities: [], selectedUserId: null }
362
}
363
});
364
365
TestBed.configureTestingModule({
366
providers: [
367
UserDataService,
368
{ provide: Store, useValue: store }
369
]
370
});
371
372
service = TestBed.inject(UserDataService);
373
});
374
375
it('should get current user', (done) => {
376
const mockUser = { id: '1', name: 'Test User' };
377
378
store.overrideSelector(service.selectCurrentUser, mockUser);
379
store.refreshState();
380
381
service.getCurrentUser().subscribe(user => {
382
expect(user).toEqual(mockUser);
383
done();
384
});
385
});
386
387
it('should load user by ID', () => {
388
spyOn(store, 'dispatch');
389
390
service.loadUser('123');
391
392
expect(store.dispatch).toHaveBeenCalledWith(
393
loadUser({ id: '123' })
394
);
395
});
396
});
397
```
398
399
### Effect Testing
400
401
```typescript
402
import { TestBed } from "@angular/core/testing";
403
import { provideMockActions } from "@ngrx/effects/testing";
404
import { Observable, of, throwError } from "rxjs";
405
import { MockStore, createMockStore } from "@ngrx/store/testing";
406
import { UserEffects } from "./user.effects";
407
408
describe('UserEffects', () => {
409
let actions$: Observable<any>;
410
let effects: UserEffects;
411
let store: MockStore;
412
let userService: jasmine.SpyObj<UserService>;
413
414
beforeEach(() => {
415
const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser']);
416
417
TestBed.configureTestingModule({
418
providers: [
419
UserEffects,
420
provideMockActions(() => actions$),
421
{ provide: UserService, useValue: userServiceSpy },
422
{ provide: Store, useValue: createMockStore() }
423
]
424
});
425
426
effects = TestBed.inject(UserEffects);
427
store = TestBed.inject(MockStore);
428
userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
429
});
430
431
it('should load user successfully', () => {
432
const user = { id: '1', name: 'Test User' };
433
const action = loadUser({ id: '1' });
434
const outcome = loadUserSuccess({ user });
435
436
actions$ = of(action);
437
userService.loadUser.and.returnValue(of(user));
438
439
effects.loadUser$.subscribe(result => {
440
expect(result).toEqual(outcome);
441
});
442
});
443
444
it('should handle load user error', () => {
445
const error = new Error('Network error');
446
const action = loadUser({ id: '1' });
447
const outcome = loadUserFailure({ error: error.message });
448
449
actions$ = of(action);
450
userService.loadUser.and.returnValue(throwError(() => error));
451
452
effects.loadUser$.subscribe(result => {
453
expect(result).toEqual(outcome);
454
});
455
});
456
});
457
```
458
459
### Selector Testing
460
461
```typescript
462
import { selectUserSummary, selectFilteredUsers } from "./user.selectors";
463
464
describe('User Selectors', () => {
465
const mockState = {
466
users: {
467
entities: [
468
{ id: '1', name: 'John', role: 'admin', active: true },
469
{ id: '2', name: 'Jane', role: 'user', active: false },
470
{ id: '3', name: 'Bob', role: 'user', active: true }
471
],
472
filters: { role: 'user', active: true }
473
}
474
};
475
476
it('should select user summary', () => {
477
const result = selectUserSummary(mockState);
478
479
expect(result).toEqual({
480
total: 3,
481
active: 2,
482
byRole: { admin: 1, user: 2 }
483
});
484
});
485
486
it('should select filtered users', () => {
487
const result = selectFilteredUsers(mockState);
488
489
expect(result).toHaveLength(1);
490
expect(result[0]).toEqual({ id: '3', name: 'Bob', role: 'user', active: true });
491
});
492
493
it('should memoize selector results', () => {
494
const result1 = selectUserSummary(mockState);
495
const result2 = selectUserSummary(mockState);
496
497
// Same reference due to memoization
498
expect(result1).toBe(result2);
499
});
500
});
501
```
502
503
### Integration Testing
504
505
```typescript
506
import { ComponentFixture, TestBed } from "@angular/core/testing";
507
import { Store } from "@ngrx/store";
508
import { MockStore, provideMockStore } from "@ngrx/store/testing";
509
import { UserFeatureComponent } from "./user-feature.component";
510
511
describe('User Feature Integration', () => {
512
let component: UserFeatureComponent;
513
let fixture: ComponentFixture<UserFeatureComponent>;
514
let store: MockStore;
515
516
beforeEach(async () => {
517
await TestBed.configureTestingModule({
518
imports: [UserFeatureComponent],
519
providers: [
520
provideMockStore({
521
initialState: { users: { entities: [], loading: false } }
522
})
523
]
524
}).compileComponents();
525
526
store = TestBed.inject(MockStore);
527
fixture = TestBed.createComponent(UserFeatureComponent);
528
component = fixture.componentInstance;
529
});
530
531
it('should handle complete user workflow', () => {
532
spyOn(store, 'dispatch');
533
534
// Initial load
535
component.ngOnInit();
536
expect(store.dispatch).toHaveBeenCalledWith(loadUsers());
537
538
// Simulate loaded state
539
store.setState({
540
users: {
541
entities: [{ id: '1', name: 'John' }],
542
loading: false,
543
selectedUserId: null
544
}
545
});
546
fixture.detectChanges();
547
548
// User selection
549
component.selectUser('1');
550
expect(store.dispatch).toHaveBeenCalledWith(selectUser({ id: '1' }));
551
552
// User update
553
component.updateUser({ id: '1', name: 'John Updated' });
554
expect(store.dispatch).toHaveBeenCalledWith(
555
updateUser({ id: '1', changes: { name: 'John Updated' } })
556
);
557
});
558
});
559
```
560
561
## Additional Testing Components
562
563
### Mock Reducer Manager
564
565
Mock implementation of ReducerManager for testing dynamic reducer scenarios.
566
567
```typescript { .api }
568
/**
569
* Mock implementation of ReducerManager for testing
570
*/
571
class MockReducerManager extends BehaviorSubject<ActionReducer<any, any>> {
572
/** Mock current reducers */
573
get currentReducers(): ActionReducerMap<any, any>;
574
575
/** Mock methods for testing dynamic reducer management */
576
addFeature(feature: StoreFeature<any, any>): void;
577
addFeatures(features: StoreFeature<any, any>[]): void;
578
removeFeature(feature: StoreFeature<any, any>): void;
579
removeFeatures(features: StoreFeature<any, any>[]): void;
580
addReducer(key: string, reducer: ActionReducer<any, any>): void;
581
addReducers(reducers: ActionReducerMap<any, any>): void;
582
removeReducer(key: string): void;
583
removeReducers(featureKeys: string[]): void;
584
ngOnDestroy(): void;
585
}
586
```
587
588
**Usage Examples:**
589
590
```typescript
591
import { TestBed } from "@angular/core/testing";
592
import { MockReducerManager } from "@ngrx/store/testing";
593
import { ReducerManager } from "@ngrx/store";
594
595
describe('Dynamic Feature Service', () => {
596
let service: DynamicFeatureService;
597
let mockReducerManager: MockReducerManager;
598
599
beforeEach(() => {
600
TestBed.configureTestingModule({
601
providers: [
602
DynamicFeatureService,
603
{ provide: ReducerManager, useClass: MockReducerManager }
604
]
605
});
606
607
service = TestBed.inject(DynamicFeatureService);
608
mockReducerManager = TestBed.inject(ReducerManager) as MockReducerManager;
609
});
610
611
it('should add feature reducer', () => {
612
spyOn(mockReducerManager, 'addReducer');
613
614
service.loadUserFeature();
615
616
expect(mockReducerManager.addReducer).toHaveBeenCalledWith(
617
'users',
618
userReducer
619
);
620
});
621
});
622
```
623
624
### Mock State
625
626
Mock implementation of the core State service for testing.
627
628
```typescript { .api }
629
/**
630
* Mock implementation of State service for testing
631
*/
632
class MockState<T> extends BehaviorSubject<T> {
633
/** Mock state as Signal */
634
readonly state: Signal<T>;
635
636
/** Constructor takes initial state */
637
constructor(initialState?: T);
638
639
/** Mock lifecycle hook */
640
ngOnDestroy(): void;
641
}
642
```
643
644
**Usage Examples:**
645
646
```typescript
647
import { TestBed } from "@angular/core/testing";
648
import { MockState } from "@ngrx/store/testing";
649
import { State } from "@ngrx/store";
650
651
describe('State-dependent Service', () => {
652
let service: StateService;
653
let mockState: MockState<AppState>;
654
655
beforeEach(() => {
656
const initialState: AppState = {
657
user: { id: null, name: '' },
658
settings: { theme: 'light' }
659
};
660
661
TestBed.configureTestingModule({
662
providers: [
663
StateService,
664
{ provide: State, useValue: new MockState(initialState) }
665
]
666
});
667
668
service = TestBed.inject(StateService);
669
mockState = TestBed.inject(State) as MockState<AppState>;
670
});
671
672
it('should react to state changes', () => {
673
const newState: AppState = {
674
user: { id: '123', name: 'John' },
675
settings: { theme: 'dark' }
676
};
677
678
mockState.next(newState);
679
680
expect(service.currentUser()).toEqual(newState.user);
681
});
682
});
683
```
684
685
### Mock Selectors
686
687
Configuration for overriding selector behavior in tests.
688
689
```typescript { .api }
690
/**
691
* Mock selector configuration for testing
692
*/
693
interface MockSelector {
694
/** Selector function to override */
695
selector: any;
696
/** Value to return from selector */
697
value: any;
698
}
699
700
/** Token for injecting mock selectors */
701
const MOCK_SELECTORS: InjectionToken<MockSelector[]>;
702
```
703
704
**Usage Examples:**
705
706
```typescript
707
import { MockSelector, MOCK_SELECTORS } from "@ngrx/store/testing";
708
709
describe('Component with Custom Selectors', () => {
710
const mockSelectors: MockSelector[] = [
711
{ selector: selectUserRole, value: 'admin' },
712
{ selector: selectUserPermissions, value: ['read', 'write', 'delete'] },
713
{ selector: selectAppConfig, value: { version: '1.0.0', debug: true } }
714
];
715
716
beforeEach(async () => {
717
await TestBed.configureTestingModule({
718
imports: [AdminComponent],
719
providers: [
720
provideMockStore({
721
initialState: defaultState,
722
selectors: mockSelectors
723
})
724
]
725
}).compileComponents();
726
});
727
728
it('should show admin features', () => {
729
fixture.detectChanges();
730
731
expect(component.userRole()).toBe('admin');
732
expect(component.canDelete()).toBe(true);
733
});
734
});
735
```
736
737
### Manual Mock Store Creation
738
739
Alternative approach for creating mock stores without TestBed.
740
741
```typescript
742
import { MockStore, createMockStore } from "@ngrx/store/testing";
743
744
// Create standalone mock store
745
const mockStore = createMockStore({
746
initialState: { counter: 0 },
747
selectors: [
748
{ selector: selectCount, value: 42 }
749
]
750
});
751
752
// Use in service testing
753
const counterService = new CounterService(mockStore);
754
755
// Override selectors dynamically
756
mockStore.overrideSelector(selectCount, 100);
757
mockStore.refreshState();
758
759
// Verify dispatch calls
760
spyOn(mockStore, 'dispatch');
761
counterService.increment();
762
expect(mockStore.dispatch).toHaveBeenCalledWith(increment());
763
```
764
765
## Testing Patterns
766
767
### Component Testing with Signals
768
769
```typescript
770
describe('Signal-based Component', () => {
771
let component: UserProfileComponent;
772
let store: MockStore;
773
774
beforeEach(() => {
775
TestBed.configureTestingModule({
776
imports: [UserProfileComponent],
777
providers: [
778
provideMockStore({
779
initialState: { user: { name: 'John', email: 'john@test.com' } }
780
})
781
]
782
});
783
784
store = TestBed.inject(MockStore);
785
component = TestBed.createComponent(UserProfileComponent).componentInstance;
786
});
787
788
it('should update signal when state changes', () => {
789
expect(component.userName()).toBe('John');
790
791
store.setState({ user: { name: 'Jane', email: 'jane@test.com' } });
792
793
expect(component.userName()).toBe('Jane');
794
});
795
});
796
```
797
798
### Async Testing with Mock Store
799
800
```typescript
801
it('should handle async selectors', fakeAsync(() => {
802
const asyncData$ = store.select(selectAsyncData);
803
let result: any;
804
805
asyncData$.subscribe(data => result = data);
806
tick();
807
808
expect(result).toEqual(initialAsyncData);
809
810
store.overrideSelector(selectAsyncData, updatedAsyncData);
811
store.refreshState();
812
tick();
813
814
expect(result).toEqual(updatedAsyncData);
815
}));
816
```
817
818
## Best Practices
819
820
1. **Mock Configuration**: Use `provideMockStore` with meaningful initial state and selectors
821
2. **Selector Overrides**: Use `overrideSelector` to test different state scenarios
822
3. **Dispatch Verification**: Verify that components dispatch correct actions with proper payloads
823
4. **State Management**: Use `setState` for complex state changes during tests
824
5. **Effect Testing**: Test effects separately using `provideMockActions` and mock services
825
6. **Integration Testing**: Test complete workflows with realistic state transitions
826
7. **Signal Testing**: Test Signal-based components with proper state updates
827
8. **Mock Services**: Use MockReducerManager and MockState for testing internal NgRx services
828
9. **Async Testing**: Use `fakeAsync` and `tick()` for testing asynchronous state changes