...

/

Testing Effects: Testing the Error Case

Testing Effects: Testing the Error Case

Learn how to write unit testing code to handle the error case of the effects.

Introduction

The ProductsEffects have been defined in such a way that when the loadProductsAction() is dispatched from our application, the loadProducts$ effect detects it by subscribing to the actions$ observable in lines 17–18. Then, in order to fetch the product data, our effects connect to the server in line 19. If the product data isn’t successfully returned from the server, we dispatch another action called getErrorAction in line 21. Let’s unit test this error case.

import { Injectable } from "@angular/core";
import { Actions, createEffect, ofType } from "@ngrx/effects";
import { catchError, map, mergeMap, of } from "rxjs";
import { Product } from "src/app/app.interfaces";
import { ProductsService } from "src/app/products.service";
import { loadProductsAction, getProductsAction, getErrorAction } from "./products.actions";
@Injectable()
export class ProductsEffects {
constructor(
private actions$: Actions,
private productsService: ProductsService
) { }
loadProducts$ = createEffect(() => {
return this.actions$.pipe(
ofType(loadProductsAction),
mergeMap(action => this.productsService.getProducts().pipe(
map((products: Product[]) => getProductsAction({ products })),
catchError(() => of(getErrorAction()))
))
)
})
}
ProductsEffects successfully fetching data from the server

Let’s visualize the success case in the following illustration:

Unit testing the error case

We can unit test the error case by following the steps detailed below.

Defining the specs

Let’s use the it function to define the specs of our test in lines 62–64.

import { TestBed } from '@angular/core/testing';
import { Actions } from '@ngrx/effects';
import { provideMockActions } from '@ngrx/effects/testing';
import { TestScheduler } from 'rxjs/testing';
import { Product } from 'src/app/app.interfaces';
import { ProductsService } from 'src/app/products.service';
import {
getProductsAction,
loadProductsAction,
} from './products.actions';
import { ProductsEffects } from './products.effects';
describe('ProductsEffects: loadProducts$', () => {
let effects: ProductsEffects;
let actions$: Actions;
let productsServiceSpy = jasmine.createSpyObj('ProductsService', [
'getProducts',
]);
let testScheduler: TestScheduler;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ProductsEffects,
provideMockActions(() => actions$),
{ provide: ProductsService, useValue: productsServiceSpy },
],
});
effects = TestBed.inject(ProductsEffects);
testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
});
it('should handle loadProductsAction and return getProductsAction on success', () => {
const products: Product[] = [
{
id: 1,
title: 'The Angular Masterclass',
author: 'Educative',
description: 'Learn all about Angular',
imgUrl: '../assets/img/the-angular-masterclass.jpg',
quantity: 124,
price: 24.38,
cart: 0,
},
];
let action = loadProductsAction();
let originalResponse = getProductsAction({ products });
testScheduler.run(({ hot, cold, expectObservable }) => {
actions$ = hot('-a', { a: action });
let mockResponse = cold('-b|', { b: products });
productsServiceSpy.getProducts.and.returnValue(mockResponse);
expectObservable(effects.loadProducts$).toBe('--c', { c: originalResponse });
});
});
it('should handle loadProductsAction and return getErrorAction action on failure', () => {
});
});
Defining the specs

Executing the testScheduler

In the ProductsEffects, we’ve interacted asynchronously with the server to fetch the data via the ProductsService. To test this asynchronous behavior, let’s use ...