Verify that your NgRx actions, reducers, selectors, and effects behave predictably and safely as your app grows.
NgRx testing focuses on unit testing the individual building blocks of state management: Actions, Reducers, Selectors, and Effects. Each piece is tested in isolation so that your global store remains predictable, debuggable, and easy to refactor.
When testing NgRx, the core idea is to treat each piece as a pure unit wherever possible:
type and payload shape.Actions stream and assert which actions or side effects are triggered.Ensure that your action creators produce the correct type and payload so reducers and effects can rely on them.
// counter.actions.ts - defines action creators
import { createAction, props } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');
export const add = createAction('[Counter] Add', props<{ value: number }>());
// counter.actions.spec.ts - unit tests for actions
import * as CounterActions from './counter.actions';
describe('Counter Actions', () => {
it('should create increment action', () => {
const action = CounterActions.increment();
expect(action.type).toBe('[Counter] Increment');
});
it('should create add action with payload', () => {
const action = CounterActions.add({ value: 5 });
expect(action.value).toBe(5);
});
});
Reducers are pure functions, so you simply pass in a state and action and assert the new state.
// counter.reducer.ts - reducer implementation
import { createReducer, on } from '@ngrx/store';
import * as CounterActions from './counter.actions';
export const initialState = 0;
export const counterReducer = createReducer(
initialState,
on(CounterActions.increment, state => state + 1),
on(CounterActions.add, (state, { value }) => state + value)
);
// counter.reducer.spec.ts - unit tests for reducer
describe('Counter Reducer', () => {
it('should increment state', () => {
const state = counterReducer(0, CounterActions.increment());
expect(state).toBe(1);
});
it('should add value to state', () => {
const state = counterReducer(1, CounterActions.add({ value: 5 }));
expect(state).toBe(6);
});
});
Selectors compute derived state. Test them with a mock state to ensure they return the expected value.
// counter.selectors.ts - selectors for derived state
import { createSelector } from '@ngrx/store';
export const selectCounter = (state: any) => state.counter;
export const selectDouble = createSelector(
selectCounter,
counter => counter * 2
);
// counter.selectors.spec.ts - unit tests for selectors
describe('Counter Selectors', () => {
it('should select double of state', () => {
const result = selectDouble.projector(3);
expect(result).toBe(6);
});
});
Effects react to actions and perform side effects. Use a mock Actions stream and assert the outcome.
// counter.effects.ts - logs every increment action
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map } from 'rxjs/operators';
import * as CounterActions from './counter.actions';
@Injectable()
export class CounterEffects {
logIncrement$ = createEffect(() =>
this.actions$.pipe(
ofType(CounterActions.increment),
map(action => {
console.log('Increment action dispatched!');
return action;
})
),
{ dispatch: false }
);
constructor(private actions$: Actions) {}
}
increment() and add() produce actions with the correct type and payload. If the string changes accidentally, the test will fail.counterReducer() directly, you confirm that the state transitions from 0 → 1 and 1 → 6 for the tested actions.selectDouble checks that the derived state is correct (3 becomes 6), ensuring components that consume it receive the right data.increment actions and logs a message. In real tests you would mock console.log or other side-effect services and assert that they are called.HttpClient or Router) instead of calling them directly.increment and add actions and write reducer tests to verify all state transitions.selectDouble (or selectTriple) and test it using the selector projector.Actions stream.decrement action, update the reducer and selectors, and add corresponding unit tests.