How to Unit Test an NgRx Reducer

Photo by Steve Johnson on Unsplash

Introduction

Before we start unit testing reducers, we’ll begin with a quick recap of NgRx reducers.

Reducers in NgRx are responsible for transitioning your application from one state to the next. Reducer functions take care of these transitions by deciding which actions to do based on the type.

Each reducer function looks at:

  • the most recent Action sent
  • the current state

In this tutorial, we’ll add unit tests for the reducer function that handles these transitions for API success/failure actions.

Example

In this example, we begin by defining a Product class with a single id property to keep the tutorial simple:

export class Product {
id: number | null;
}

In a file called product-api.actions.ts, we define our API success and failure actions for loading a list of products:

import { Product } from '../../product';
import { createAction, props } from '@ngrx/store';
export const loadProductsSuccess = createAction(
'[Product API] Load Success',
props<{ products: Product[] }>()
);
export const loadProductsFailure = createAction(
'[Product API] Load Fail',
props<{ error: string }>()
);

The product.reducer.ts file below defines the initial state and a product reducer function that handles the transitions from one state to another, depending on which action is handled i.e. whether the products were loaded successfully or not.

The initialState defines the initial value of the product state, with no products and an empty error string by default as expected.

If products are loaded successfully, the ProductState is replaced with the current state (…state) but with products populated and error message emptied.

Otherwise, if there was a problem loading products, the ProductState is replaced with the current state but with no products and an error message.

import { Product } from '../product';
import { createReducer, on } from '@ngrx/store';
import { ProductApiActions } from './actions';
export interface ProductState {
id: number | null;
products: Product[];
error: string;
}
export const initialState: ProductState = {
id: null,
products: [],
error: ''
};
export const productReducer = createReducer<ProductState>(
initialState,
on(ProductApiActions.loadProductsSuccess, (state, action): ProductState => {
return {
...state,
products: action.products,
error: ''

};
}),
on(ProductApiActions.loadProductsFailure, (state, action): ProductState => {
return {
...state,
products: [],
error: action.error

};
})
);

Tests

In a new file product.reducer.spec.ts, we define our two tests for product load success/failure API actions.

For the success test, we add a single product with product id 1 to an array, checking that the result from the product reducer is the same.

For the failure test, we simply check that the error message matches that of the action.

import { ProductApiActions } from "./actions";
import * as fromProducts from './product.reducer';
import { Product } from "../product";
describe('Product reducers', () => {
it('[Product API] Load Products Success', () => {

// arrange
const product = new Product();
product.id = 1;
const products = [product];
const action = ProductApiActions.loadProductsSuccess(
{ products }
);

const expectedState: fromProducts.ProductState = {
...fromProducts.initialState,
products
};

// act
const result = fromProducts
.productReducer(fromProducts.initialState, action);
// assert
expect(result).toEqual(expectedState);
});
it('[Product API] Load Products Failure', () => { // arrange
const error = 'error';
const action = ProductApiActions.loadProductsFailure(
{
error
});
const expectedState: fromProducts.ProductState = {
...fromProducts.initialState,
error
};
// act
const result = fromProducts
.productReducer(fromProducts.initialState, action);
// assert
expect(result).toEqual(expectedState);
});
});

Both tests are passing, as shown below:

Thanks for reading! Let me know what you think in the comments section below, and don’t forget to subscribe. 👍

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
George Marklow

George Marklow

George is a software engineer, author, blogger, and abstract artist who believes in helping others to make us happier and healthier.