RxJS powered Redux state management for Angular applications
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Testing utilities provide comprehensive mock implementations and testing helpers for NgRx Store, enabling effective unit testing of components, services, and state management logic.
Creates mock store providers for testing with TestBed configuration.
/**
* Creates mock store providers for testing environments
* @param config - Mock store configuration with initial state and selectors
* @returns Provider array for TestBed configuration
*/
function provideMockStore<T = any>(
config?: MockStoreConfig<T>
): (ValueProvider | ExistingProvider | FactoryProvider)[];
/**
* Configuration for mock store setup
*/
interface MockStoreConfig<T> {
/** Initial state for mock store */
initialState?: T;
/** Mock selectors with predefined return values */
selectors?: MockSelector[];
}Usage Examples:
import { TestBed } from "@angular/core/testing";
import { provideMockStore, MockStore } from "@ngrx/store/testing";
import { selectUser, selectUserName } from "./user.selectors";
describe('UserComponent', () => {
let store: MockStore;
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserComponent],
providers: [
provideMockStore({
initialState: {
user: {
entities: [
{ id: '1', name: 'John Doe', email: 'john@example.com' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com' }
],
selectedUserId: '1',
loading: false,
error: null
}
},
selectors: [
{ selector: selectUser, value: { id: '1', name: 'John Doe' } },
{ selector: selectUserName, value: 'John Doe' }
]
})
]
}).compileComponents();
store = TestBed.inject(MockStore);
fixture = TestBed.createComponent(UserComponent);
component = fixture.componentInstance;
});
it('should display user name', () => {
fixture.detectChanges();
expect(component.userName()).toBe('John Doe');
});
});Creates a standalone mock store instance without TestBed for service and effect testing.
/**
* Creates mock store instance outside of TestBed
* @param config - Mock store configuration
* @returns MockStore instance for testing
*/
function createMockStore<T>(config?: MockStoreConfig<T>): MockStore<T>;Usage Examples:
import { createMockStore } from "@ngrx/store/testing";
import { UserEffects } from "./user.effects";
describe('UserEffects', () => {
let effects: UserEffects;
let store: MockStore;
let userService: jasmine.SpyObj<UserService>;
beforeEach(() => {
const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser', 'updateUser']);
store = createMockStore({
initialState: {
user: { entities: [], loading: false, error: null }
}
});
TestBed.configureTestingModule({
providers: [
UserEffects,
{ provide: Store, useValue: store },
{ provide: UserService, useValue: userServiceSpy }
]
});
effects = TestBed.inject(UserEffects);
userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
});
it('should load user data', () => {
userService.loadUser.and.returnValue(of({ id: '1', name: 'Test User' }));
store.dispatch(loadUser({ id: '1' }));
expect(userService.loadUser).toHaveBeenCalledWith('1');
});
});Enhanced Store implementation with testing-specific methods for overriding selectors and managing state.
/**
* Mock implementation of Store for testing
*/
class MockStore<T> extends Store<T> {
/**
* Override selector return value for testing
* @param selector - Selector to override
* @param value - Value to return from selector
*/
overrideSelector<Result>(
selector: MemoizedSelector<any, Result>,
value: Result
): void;
/**
* Refresh mock state and notify subscribers
*/
refreshState(): void;
/**
* Set mock state directly
* @param nextState - New state to set
*/
setState(nextState: T): void;
/**
* Reset all selector overrides
*/
resetSelectors(): void;
}Usage Examples:
import { MockStore } from "@ngrx/store/testing";
import { selectUserPreferences, selectTheme } from "./ui.selectors";
describe('UI Component Tests', () => {
let store: MockStore;
let component: ThemeToggleComponent;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideMockStore()]
});
store = TestBed.inject(MockStore);
component = TestBed.inject(ThemeToggleComponent);
});
it('should handle theme changes', () => {
// Override selector return value
store.overrideSelector(selectTheme, 'dark');
store.refreshState();
expect(component.currentTheme()).toBe('dark');
// Change selector value during test
store.overrideSelector(selectTheme, 'light');
store.refreshState();
expect(component.currentTheme()).toBe('light');
});
it('should handle complex state changes', () => {
const mockPreferences = {
theme: 'auto',
language: 'en',
notifications: true
};
store.overrideSelector(selectUserPreferences, mockPreferences);
store.refreshState();
expect(component.preferences()).toEqual(mockPreferences);
// Direct state manipulation
store.setState({
...store.state(),
ui: {
preferences: { ...mockPreferences, theme: 'dark' }
}
});
expect(component.preferences().theme).toBe('dark');
});
});Configuration for mocking individual selectors with predefined return values.
/**
* Configuration for mocking individual selectors
*/
interface MockSelector {
/** The selector to mock */
selector: MemoizedSelector<any, any> | string;
/** The value to return from the selector */
value: any;
}Usage Examples:
import { selectAllUsers, selectActiveUsers, selectUserCount } from "./user.selectors";
const mockSelectors: MockSelector[] = [
{
selector: selectAllUsers,
value: [
{ id: '1', name: 'John', active: true },
{ id: '2', name: 'Jane', active: false }
]
},
{
selector: selectActiveUsers,
value: [{ id: '1', name: 'John', active: true }]
},
{
selector: selectUserCount,
value: 2
}
];
TestBed.configureTestingModule({
providers: [
provideMockStore({
selectors: mockSelectors
})
]
});import { ComponentFixture, TestBed } from "@angular/core/testing";
import { MockStore } from "@ngrx/store/testing";
import { UserListComponent } from "./user-list.component";
describe('UserListComponent', () => {
let component: UserListComponent;
let fixture: ComponentFixture<UserListComponent>;
let store: MockStore;
const initialState = {
users: {
entities: [],
loading: false,
error: null,
filters: { search: '', role: 'all' }
}
};
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserListComponent],
providers: [provideMockStore({ initialState })]
}).compileComponents();
store = TestBed.inject(MockStore);
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
});
it('should display loading state', () => {
store.setState({
...initialState,
users: { ...initialState.users, loading: true }
});
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.loading')).toBeTruthy();
});
it('should dispatch search action', () => {
spyOn(store, 'dispatch');
component.searchUsers('john');
expect(store.dispatch).toHaveBeenCalledWith(
searchUsers({ query: 'john' })
);
});
it('should handle error state', () => {
const errorMessage = 'Failed to load users';
store.setState({
...initialState,
users: { ...initialState.users, error: errorMessage }
});
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain(errorMessage);
});
});import { TestBed } from "@angular/core/testing";
import { createMockStore, MockStore } from "@ngrx/store/testing";
import { UserDataService } from "./user-data.service";
describe('UserDataService', () => {
let service: UserDataService;
let store: MockStore;
beforeEach(() => {
store = createMockStore({
initialState: {
users: { entities: [], selectedUserId: null }
}
});
TestBed.configureTestingModule({
providers: [
UserDataService,
{ provide: Store, useValue: store }
]
});
service = TestBed.inject(UserDataService);
});
it('should get current user', (done) => {
const mockUser = { id: '1', name: 'Test User' };
store.overrideSelector(service.selectCurrentUser, mockUser);
store.refreshState();
service.getCurrentUser().subscribe(user => {
expect(user).toEqual(mockUser);
done();
});
});
it('should load user by ID', () => {
spyOn(store, 'dispatch');
service.loadUser('123');
expect(store.dispatch).toHaveBeenCalledWith(
loadUser({ id: '123' })
);
});
});import { TestBed } from "@angular/core/testing";
import { provideMockActions } from "@ngrx/effects/testing";
import { Observable, of, throwError } from "rxjs";
import { MockStore, createMockStore } from "@ngrx/store/testing";
import { UserEffects } from "./user.effects";
describe('UserEffects', () => {
let actions$: Observable<any>;
let effects: UserEffects;
let store: MockStore;
let userService: jasmine.SpyObj<UserService>;
beforeEach(() => {
const userServiceSpy = jasmine.createSpyObj('UserService', ['loadUser']);
TestBed.configureTestingModule({
providers: [
UserEffects,
provideMockActions(() => actions$),
{ provide: UserService, useValue: userServiceSpy },
{ provide: Store, useValue: createMockStore() }
]
});
effects = TestBed.inject(UserEffects);
store = TestBed.inject(MockStore);
userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
});
it('should load user successfully', () => {
const user = { id: '1', name: 'Test User' };
const action = loadUser({ id: '1' });
const outcome = loadUserSuccess({ user });
actions$ = of(action);
userService.loadUser.and.returnValue(of(user));
effects.loadUser$.subscribe(result => {
expect(result).toEqual(outcome);
});
});
it('should handle load user error', () => {
const error = new Error('Network error');
const action = loadUser({ id: '1' });
const outcome = loadUserFailure({ error: error.message });
actions$ = of(action);
userService.loadUser.and.returnValue(throwError(() => error));
effects.loadUser$.subscribe(result => {
expect(result).toEqual(outcome);
});
});
});import { selectUserSummary, selectFilteredUsers } from "./user.selectors";
describe('User Selectors', () => {
const mockState = {
users: {
entities: [
{ id: '1', name: 'John', role: 'admin', active: true },
{ id: '2', name: 'Jane', role: 'user', active: false },
{ id: '3', name: 'Bob', role: 'user', active: true }
],
filters: { role: 'user', active: true }
}
};
it('should select user summary', () => {
const result = selectUserSummary(mockState);
expect(result).toEqual({
total: 3,
active: 2,
byRole: { admin: 1, user: 2 }
});
});
it('should select filtered users', () => {
const result = selectFilteredUsers(mockState);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({ id: '3', name: 'Bob', role: 'user', active: true });
});
it('should memoize selector results', () => {
const result1 = selectUserSummary(mockState);
const result2 = selectUserSummary(mockState);
// Same reference due to memoization
expect(result1).toBe(result2);
});
});import { ComponentFixture, TestBed } from "@angular/core/testing";
import { Store } from "@ngrx/store";
import { MockStore, provideMockStore } from "@ngrx/store/testing";
import { UserFeatureComponent } from "./user-feature.component";
describe('User Feature Integration', () => {
let component: UserFeatureComponent;
let fixture: ComponentFixture<UserFeatureComponent>;
let store: MockStore;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserFeatureComponent],
providers: [
provideMockStore({
initialState: { users: { entities: [], loading: false } }
})
]
}).compileComponents();
store = TestBed.inject(MockStore);
fixture = TestBed.createComponent(UserFeatureComponent);
component = fixture.componentInstance;
});
it('should handle complete user workflow', () => {
spyOn(store, 'dispatch');
// Initial load
component.ngOnInit();
expect(store.dispatch).toHaveBeenCalledWith(loadUsers());
// Simulate loaded state
store.setState({
users: {
entities: [{ id: '1', name: 'John' }],
loading: false,
selectedUserId: null
}
});
fixture.detectChanges();
// User selection
component.selectUser('1');
expect(store.dispatch).toHaveBeenCalledWith(selectUser({ id: '1' }));
// User update
component.updateUser({ id: '1', name: 'John Updated' });
expect(store.dispatch).toHaveBeenCalledWith(
updateUser({ id: '1', changes: { name: 'John Updated' } })
);
});
});Mock implementation of ReducerManager for testing dynamic reducer scenarios.
/**
* Mock implementation of ReducerManager for testing
*/
class MockReducerManager extends BehaviorSubject<ActionReducer<any, any>> {
/** Mock current reducers */
get currentReducers(): ActionReducerMap<any, any>;
/** Mock methods for testing dynamic reducer management */
addFeature(feature: StoreFeature<any, any>): void;
addFeatures(features: StoreFeature<any, any>[]): void;
removeFeature(feature: StoreFeature<any, any>): void;
removeFeatures(features: StoreFeature<any, any>[]): void;
addReducer(key: string, reducer: ActionReducer<any, any>): void;
addReducers(reducers: ActionReducerMap<any, any>): void;
removeReducer(key: string): void;
removeReducers(featureKeys: string[]): void;
ngOnDestroy(): void;
}Usage Examples:
import { TestBed } from "@angular/core/testing";
import { MockReducerManager } from "@ngrx/store/testing";
import { ReducerManager } from "@ngrx/store";
describe('Dynamic Feature Service', () => {
let service: DynamicFeatureService;
let mockReducerManager: MockReducerManager;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
DynamicFeatureService,
{ provide: ReducerManager, useClass: MockReducerManager }
]
});
service = TestBed.inject(DynamicFeatureService);
mockReducerManager = TestBed.inject(ReducerManager) as MockReducerManager;
});
it('should add feature reducer', () => {
spyOn(mockReducerManager, 'addReducer');
service.loadUserFeature();
expect(mockReducerManager.addReducer).toHaveBeenCalledWith(
'users',
userReducer
);
});
});Mock implementation of the core State service for testing.
/**
* Mock implementation of State service for testing
*/
class MockState<T> extends BehaviorSubject<T> {
/** Mock state as Signal */
readonly state: Signal<T>;
/** Constructor takes initial state */
constructor(initialState?: T);
/** Mock lifecycle hook */
ngOnDestroy(): void;
}Usage Examples:
import { TestBed } from "@angular/core/testing";
import { MockState } from "@ngrx/store/testing";
import { State } from "@ngrx/store";
describe('State-dependent Service', () => {
let service: StateService;
let mockState: MockState<AppState>;
beforeEach(() => {
const initialState: AppState = {
user: { id: null, name: '' },
settings: { theme: 'light' }
};
TestBed.configureTestingModule({
providers: [
StateService,
{ provide: State, useValue: new MockState(initialState) }
]
});
service = TestBed.inject(StateService);
mockState = TestBed.inject(State) as MockState<AppState>;
});
it('should react to state changes', () => {
const newState: AppState = {
user: { id: '123', name: 'John' },
settings: { theme: 'dark' }
};
mockState.next(newState);
expect(service.currentUser()).toEqual(newState.user);
});
});Configuration for overriding selector behavior in tests.
/**
* Mock selector configuration for testing
*/
interface MockSelector {
/** Selector function to override */
selector: any;
/** Value to return from selector */
value: any;
}
/** Token for injecting mock selectors */
const MOCK_SELECTORS: InjectionToken<MockSelector[]>;Usage Examples:
import { MockSelector, MOCK_SELECTORS } from "@ngrx/store/testing";
describe('Component with Custom Selectors', () => {
const mockSelectors: MockSelector[] = [
{ selector: selectUserRole, value: 'admin' },
{ selector: selectUserPermissions, value: ['read', 'write', 'delete'] },
{ selector: selectAppConfig, value: { version: '1.0.0', debug: true } }
];
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AdminComponent],
providers: [
provideMockStore({
initialState: defaultState,
selectors: mockSelectors
})
]
}).compileComponents();
});
it('should show admin features', () => {
fixture.detectChanges();
expect(component.userRole()).toBe('admin');
expect(component.canDelete()).toBe(true);
});
});Alternative approach for creating mock stores without TestBed.
import { MockStore, createMockStore } from "@ngrx/store/testing";
// Create standalone mock store
const mockStore = createMockStore({
initialState: { counter: 0 },
selectors: [
{ selector: selectCount, value: 42 }
]
});
// Use in service testing
const counterService = new CounterService(mockStore);
// Override selectors dynamically
mockStore.overrideSelector(selectCount, 100);
mockStore.refreshState();
// Verify dispatch calls
spyOn(mockStore, 'dispatch');
counterService.increment();
expect(mockStore.dispatch).toHaveBeenCalledWith(increment());describe('Signal-based Component', () => {
let component: UserProfileComponent;
let store: MockStore;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [UserProfileComponent],
providers: [
provideMockStore({
initialState: { user: { name: 'John', email: 'john@test.com' } }
})
]
});
store = TestBed.inject(MockStore);
component = TestBed.createComponent(UserProfileComponent).componentInstance;
});
it('should update signal when state changes', () => {
expect(component.userName()).toBe('John');
store.setState({ user: { name: 'Jane', email: 'jane@test.com' } });
expect(component.userName()).toBe('Jane');
});
});it('should handle async selectors', fakeAsync(() => {
const asyncData$ = store.select(selectAsyncData);
let result: any;
asyncData$.subscribe(data => result = data);
tick();
expect(result).toEqual(initialAsyncData);
store.overrideSelector(selectAsyncData, updatedAsyncData);
store.refreshState();
tick();
expect(result).toEqual(updatedAsyncData);
}));provideMockStore with meaningful initial state and selectorsoverrideSelector to test different state scenariossetState for complex state changes during testsprovideMockActions and mock servicesfakeAsync and tick() for testing asynchronous state changes