NgRx Testing - The Complete Guide

NgRx Testing - The Complete Guide

In NgRx there are different ways to test different parts of the app. We have reducers, actions, selectors and effects which all need its own know-how to test efficiently. This post will show you how to test NgRx apps, what to test for and how to create the tests with ease.

The main secret behind having better test coverage in an Angular app is to reduce the friction it takes to write tests and enforce test coverage by setting a test coverage threshold to be checked on every commit. NgRx and reactive testing is an area where many people get confused because it seems hard to write the tests to provoke and expect the desired behavior.

In NgRx there are different ways to test different parts of the app. We have reducersactionsselectors and effects which all need its own know-how to test efficiently.

This post will show you how to test NgRx apps, what to test for and how to create the tests with ease.

Testing reducers

You are most likely going to spend most of your time writing tests for reducers. Reducers are the easiest to test as they are pure functions, and you should simply assert that you get the right state given the provided inputs.

Consider the following reducer:

export const todoListReducers = (
      lastState: TodoListState = new TodoListInitState(),
      action: GenericAction<TodoListActionTypes, any>
    ): TodoListState => {
      switch (action.type) {
        case TodoListActionTypes.LoadTodoList:
          return loadTodoItems(lastState, action);
        case TodoListActionTypes.TodoItemsLoaded:
          return todoItemsLoaded(lastState, action);
        case TodoListActionTypes.TodoItemsLoadFailed:
          return todoItemsLoadFailed(lastState, action);
        case TodoListActionTypes.TodoItemCreated:
          return todoItemCreatedReducer(lastState, action);
        case TodoListActionTypes.TodoItemsLoadFailed:
          return todoItemsLoadFailed(lastState, action);
        case TodoListActionTypes.TodoItemDeleted:
          return todoItemDeletedReducer(lastState, action);
        case TodoListActionTypes.TodoItemUpdated:
          return todoItemUpdatedReducer(lastState, action);
        case TodoListActionTypes.TodoItemCompleted:
          return todoItemCompletedReducer(lastState, action);


        default:
          return lastState;
      }
    };

You normally start with testing the default case:

describe('default', () => {
      it('should return init state', () => {
        const noopAction = new GenericAction('noop' as TodoListActionTypes);
        const newState = todoListReducers(undefined, noopAction);


        const initState = new TodoListInitState();
        expect(newState).toEqual(initState);
      });
    });

This case should simply return the initial state when you call the reducer with a “noop” action, that is, an action that doesn’t match any reducers.

Next, let’s look at how to test the TodoItemsLoaded case:

 describe('loadTodoItems', () => {
        it('should return isLoading true', () => {
          const initState = new TodoListInitState();
          const loadTodoItemsAction = new GenericAction(TodoListActionTypes.LoadTodoList);
          const newState = todoListReducers(initState, loadTodoItemsAction);


          expect(newState.isLoading).toBe(true);
        });
      });

Note that I’m using the [GenericAction](https://gist.github.com/92b66cd258dbbb21f1ffae765e100fd3 "GenericAction") which is a generic action creator that you can use for reducing the amount of boilerplate that you often see in NgRx apps. Here I’m LoadTodoList and is asserting that the state is not set to isLoading.

And lastly a failure reducer:

 describe('todoItemsLoadFailed', () => {
        it('should return isLoading false and error', () => {
          const initState = new TodoListInitState();
          const error = new Error('http error');
          const loadTodoItemsAction = new GenericAction(TodoListActionTypes.TodoItemsLoadFailed, error);
          const newState = todoListReducers(initState, loadTodoItemsAction);


          expect(newState.isLoading).toBe(false);
          expect(newState.errors).toBe(error);
        });
      });

This is triggering the TodoItemsLoadFailed and with the error as payload. It expects that the new state is not loading and contains the error.

All of this is fairly simple to do so this is a good use case for practicing the TDD approach because of the easy testability and it ensures that your business logic follows the requirements. TDD becomes harder as you are working closer to the UI as you might want a more exploratory development approach for this, eg. mocking up a new proof of concept prototype.

Testing Actions

Actions are not containing any business logic so this provides less value to test. They are only used to trigger a reducer or an effect, which is already covered by type-safety by using Typescript. You might anyway want to write tests for your action dispatchers for the sake of enforcing a specific coverage level and “double checking” that the right action is being dispatched.

To reduce the boilerplate and need for injecting the store in every container component I like to wrap actions in a service, which gives makes it simpler to use:

@Injectable({ providedIn: 'root' })
export class TodoListActions {
&nbsp;constructor(private store: Store<TodoListState>) {}

&nbsp;public loadTodoList(): void {
&nbsp;&nbsp;this.store.dispatch(new LoadTodoList());
&nbsp;}

&nbsp;public deleteTodo(id: string) {
&nbsp;&nbsp;this.store.dispatch(new TodoItemDeleted(id));
&nbsp;}

&nbsp;public todoItemUpdated(id: string) {
&nbsp;&nbsp;this.store.dispatch(new TodoItemDeleted(id));
&nbsp;}

&nbsp;public todoItemCompleted(id: string) {
&nbsp;&nbsp;this.store.dispatch(new TodoItemCompleted(id));
&nbsp;}
}

To test these you would just assert that the store is dispatching the right action when you call these methods:

&nbsp;describe('loadTodoList', () => {
&nbsp;&nbsp;it('should dispatch load todolist action', () => {
&nbsp;&nbsp;&nbsp;const expectedAction = new LoadTodoList();
&nbsp;&nbsp;&nbsp;const store = jasmine.createSpyObj<Store<TodoListState>>('store', ['dispatch']);

&nbsp;&nbsp;&nbsp;const todoListActions = new TodoListActions(store);
&nbsp;&nbsp;&nbsp;todoListActions.loadTodoList();

&nbsp;&nbsp;&nbsp;expect(store.dispatch).toHaveBeenCalledWith(expectedAction);
&nbsp;&nbsp;});
&nbsp;});

Testing Effects

Testing effects become slightly more complicated as you here need to assert a reactive resultas well as trigger an effect.

To assert that an effect returns the right observable stream, we can use Rx Marbles.

Rx Marbles

Rx Marbles seems scary and complicated at first, but it is actually pretty simple when you focus on the core:

It is a standard for describing observable streams. Since we are using Jasmine in our Angular apps, we are going to use the Rx Marbles implementation Jasmine-marbles.

Rx Marbles in simple words

Rx Marbles is a standard for defining observable streams. We use it as an easy way to generate an observable stream. To create them there are some symbols you need to know:

  1. You can create either a cold or a hot observable with Marbles. These are created with the functions: cold and hot
  2. “-” is 10 frames, for indicating time has passed. Every one of the symbols below will also take up 10 frames, which is a type of virtual time to separate occurrences of events. They are not bound to any real-time measure
  3. “|” means completed observable
  4. “#” means error, you can specify the error by setting it as the third argument to cold or hot
  5. “()” can wrap a couple of events that should happen on the same timeframe
  6. [a-z 0-9] Everything else is variables, that can be set with the second argument in cold or hot

That is all you need for creating these NgRx tests. If you want to read more about Rx Marbles you can find good info here.

Using Rx Marbles in tests

Take a look at the following effect:

@Injectable()
export class TodoListEffects {
&nbsp;constructor(private actions$: Actions, private todoListService: TodoListService) {}

&nbsp;@Effect()
&nbsp;loadTodoList$ = this.actions$.pipe(
&nbsp;&nbsp;ofType(TodoListActionTypes.LoadTodoList),
&nbsp;&nbsp;exhaustMap(() => this.todoListService.getTodos()),
&nbsp;&nbsp;map((todoList) => new TodoItemsLoaded(todoList)),
&nbsp;&nbsp;catchError((error: Error) => of(new TodoItemsLoadFailed(error)))
&nbsp;);
}

Let’s write tests for the two paths: success and failure.

&nbsp;&nbsp;it('should return a stream with todo list loaded action', () => {
&nbsp;&nbsp;&nbsp;const todoList: TODOItem[] = [{ title: '', id: '1', description: '' }];
&nbsp;&nbsp;&nbsp;const action = new LoadTodoList();
&nbsp;&nbsp;&nbsp;const outcome = new TodoItemsLoaded(todoList);

&nbsp;&nbsp;&nbsp;actions = hot('-a', { a: action });
&nbsp;&nbsp;&nbsp;const response = cold('-a|', { a: todoList });
&nbsp;&nbsp;&nbsp;todoListService.getTodos.and.returnValue(response);

&nbsp;&nbsp;&nbsp;const expected = cold('--b', { b: outcome });
&nbsp;&nbsp;&nbsp;expect(effects.loadTodoList$).toBeObservable(expected);
&nbsp;&nbsp;});

We set the action to be dispatched by making it a hot observable that is waiting for 10 frames and then emitting the action. This is used to trigger the effect under test.

We specify the getTodos response as a cold observable because it should only run when the test is calling it. This is waiting for 10 frames, returning todoList and then completing. Lastly, we expect that it is waiting for 10+10=20 frames and then returns a stream with the TodoItemsLoaded action.

Test failed case:

&nbsp;it('should fail and return an action with the error', () => {
&nbsp;&nbsp;&nbsp;const action = new LoadTodoList();
&nbsp;&nbsp;&nbsp;const error = new Error('some error') as any;
&nbsp;&nbsp;&nbsp;const outcome = new TodoItemsLoadFailed(error);

&nbsp;&nbsp;&nbsp;actions = hot('-a', { a: action });
&nbsp;&nbsp;&nbsp;const response = cold('-#|', {}, error);
&nbsp;&nbsp;&nbsp;todoListService.getTodos.and.returnValue(response);

&nbsp;&nbsp;&nbsp;const expected = cold('--(b|)', { b: outcome });
&nbsp;&nbsp;&nbsp;expect(effects.loadTodoList$).toBeObservable(expected);
&nbsp;&nbsp;});

To test for the failed case we first trigger the effect by setting the actions as a hot observable. Then we make the response from getTodos wait 10 frames, throw an error and then return. Again since 20 frames have passed until the stream is completed, the expected stream is waiting for 20 frames and then it will return the TodoItemsLoadFailed and complete on the same frame (because the action is wrapped in the of operator, which completes instantaneously).

The complete spec looks like this:

import { TestBed } from '@angular/core/testing';
    import { TodoListService } from '@app/core/todo-list/todo-list.service';
    import { TODOItem } from '@app/shared/models/todo-item';
    import { provideMockActions } from '@ngrx/effects/testing';
    import { cold, hot } from 'jasmine-marbles';
    import { Observable } from 'rxjs';
    import { LoadTodoList, TodoItemsLoaded, TodoItemsLoadFailed } from './todo-list.actions';
    import { TodoListEffects } from './todo-list.effects';


    describe('TodoListEffects', () => {
      let actions: Observable<any>;


      let effects: TodoListEffects;
      let todoListService: jasmine.SpyObj<TodoListService>;


      beforeEach(() => {
        TestBed.configureTestingModule({
          providers: [
            TodoListEffects,
            provideMockActions(() => actions),
            {
              provide: TodoListService,
              useValue: {
                getTodos: jasmine.createSpy()
              }
            }
          ]
        });


        effects = TestBed.get(TodoListEffects);
        todoListService = TestBed.get(TodoListService);
      });


      describe('loadTodoList', () => {
        it('should return a stream with todo list loaded action', () => {
          const todoList: TODOItem[] = [{ title: '', id: '1', description: '' }];
          const action = new LoadTodoList();
          const outcome = new TodoItemsLoaded(todoList);


          actions = hot('-a', { a: action });
          const response = cold('-a|', { a: todoList });
          todoListService.getTodos.and.returnValue(response);


          const expected = cold('--b', { b: outcome });
          expect(effects.loadTodoList$).toBeObservable(expected);
        });


        it('should fail and return an action with the error', () => {
          const action = new LoadTodoList();
          const error = new Error('some error') as any;
          const outcome = new TodoItemsLoadFailed(error);


          actions = hot('-a', { a: action });
          const response = cold('-#|', {}, error);
          todoListService.getTodos.and.returnValue(response);


          const expected = cold('--(b|)', { b: outcome });
          expect(effects.loadTodoList$).toBeObservable(expected);
        });
      });
    });

Note how you need to install jasmine-marbles:

npm i -D jasmine-marbles

And need to hook in the actions to trigger effects with:

provideMockActions(() => actions)

This should cover the typical use cases when creating tests for effects.

Testing selectors

For ensuring type-safety selection of the store you should not, use the store directly in your feature services but instead reference the store’s selector file, which acts as a facade to the store. This also ensures that your testing becomes easier, as you don’t have to deal with a mock store.

For testing the selector the same principles as with testing actions applies. It is already typesafe by using Typescript and it is interacting closely with the NgRx framework which is already covered in tests. But if your app needs 100 % coverage and you want to “double check”, you will need to assert that the store is calling the right selector function.

Consider the following selector class:

export const getTodolistState = createFeatureSelector<TodoListState>('todoList');


    export const todoListSelectorFn = createSelector(
      getTodolistState,
      (todoListState) => todoListState.todos
    );


    @Injectable({
      providedIn: 'root'
    })
    export class TodoListSelector {
      /**
       *
       */
      constructor(private store: Store<TodoListState>) {}
      /**
       * getTodoList
       */
      public getTodoList() {
        return this.store.select(todoListSelectorFn);
      }
    }

You would test it like this:

 it('should return call the todoListSelectorFn', () => {
        const store = jasmine.createSpyObj<Store<TodoListState>>('store', ['select']);


        const todoListSelector = new TodoListSelector(store);
        todoListSelector.getTodoList();


        expect(store.select).toHaveBeenCalledWith(todoListSelectorFn);
      });

Here we are simply testing that the store is called with the right selector function.

Another benefit of having this selector facade is that it is easier to mock out the selector facade than if you were to use the store directly. You can generate a mock automatically using this service mock generator from my All You Need to Know About Mocking in Angular Tests blog post. Again reduced friction = more test coverage and time for fun in life, unless writing tests are your number one thing.

We also need to test the actual selector function in isolation:

it('should return the todoList', () => {
      const todos = [new TODOItem('todo1', 'todo1')];


      const todoListState = {
        todos: todos,
        isLoading: true
      } as TodoListState;


      expect(todoListSelectorFn.projector(todoListState)).toEqual(todos);
    });

NgRxselector functions have a projector method which allows you to skip all previous selectors and only run the last selector function for the specified state. That way, you don’t need to stub the whole NgRx state. We expect that this will give us the todo list.

Testing NgRx facade without a selector service using MockStore

I recommend NOT getting your architecture married to NgRx or any other state management framework as this will make it hard to change later and make the application architecture less clean. Instead, I recommend you create a facade/sandbox to decouple UI from business logic.

You might not want to have a selector service wrapping your selectors and instead use the selector functions directly in your facade. If you do this instead, I recommend you use MockStore to set the state using the setState method.

This is the complete example:

import { TestBed } from '@angular/core/testing';
    import { Store } from '@ngrx/store';
    import { MockStore, provideMockStore } from '@ngrx/store/testing';
    import { first } from 'rxjs/operators';


    import { TodoListService } from '@app/core/todo-list/todo-list.service';
    import { TODOItem } from '@app/shared/models/todo-item';
    import { TodoListState } from './redux-api/todo-list.model';


    describe('Service: TodoList', () => {
      let todoListService: TodoListService;
      let store: MockStore<TodoListState>;
      const initialState = {};


      beforeEach(() => {
        TestBed.configureTestingModule({
          providers: [
            todoListService,
            {
              provide:
            },
            provideMockStore({ initialState }),
          ],
        });


        todoListService = TestBed.get(todoListService);
        store = TestBed.get(Store);
      });


      it('should return the todo list', (done) => {


        const todoList = [{
          id: 'some-todo-item1'
        } as TODOItem];
        store.setState({ todos: todoList, isLoading: false })


        todoListService.todoList$.pipe(first()).subscribe((todoList) => {
          expect(todoList).toBe(todoList);
          done();
        });
      });
    });

Complete Demo Repository

You can find a complete demo of NgRx testing a Todo app on my Github.

Conclusion

In this post, we looked at how to test Angular apps with NgRx. We looked at how to test the different types: reducers, effects, actions, and selectors. Of these tests, reducers were the simplest to test as they are pure functions. We looked at how to test effects using Jasmine Marbles, which allowed us to trigger the effect under test as well as specify the expected observable stream the effect should create. I also showed you some tips forreducing boilerplate with selectors and actions, you can use to make it more simple and frictionless to work with NgRx.

This post was originally published here

typescript javascript angular web-development testing

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Wondering how to upgrade your skills in the pandemic? Here's a simple way you can do it.

Corona Virus Pandemic has brought the world to a standstill. Countries are on a major lockdown. Schools, colleges, theatres, gym, clubs, and all other public

Hire Web Developer

Looking for an attractive & user-friendly web developer? HourlyDeveloper.io, a leading web, and mobile app development company, offers web developers for hire through flexible engagement models. You can **[Hire Web...

Why Web Development is Important for your Business

With the rapid development in technology, the old ways to do business have changed completely. A lot more advanced and developed ways are ...