DEV Community

Alex Fallenstedt
Alex Fallenstedt

Posted on

Testing NgRx Effects

alex fallenstedt photography

You are walking down the street sipping some coffee and you ask yourself, “How can I test my ngrx@7.4.0 effects?”. If you’re asking that question because you're not satisfied with the developer experience of jasmine-marbles then this might help you.

A Basic Effect

@Injectable()
export class RouterHistoryEffects {

 @Effect()
 addRouteToHistory = this.actions.pipe(
   ofType(ROUTER_NAVIGATED),
   map((action: RouterNavigatedAction) => action.payload.routerState.url),
   switchMap((url: string) => {
     return of(new fromRouterHistoryActions.AddRouteToHistory(url));
   })
 );

 constructor(private actions: Actions<RouterHistoryActions>) { }
}
Enter fullscreen mode Exit fullscreen mode

Here we have an action that is dispatched from somewhere. It’s supposed to return a new action. When this is consumed by ngrx, addRouteToHistory subscribes to the action stream and can publish to your action stream. More reading here

Knowing this, our test should perform the same scenario. We should subscribe to addRouteToHistory and listen for events to happen.

import { TestBed, async } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { ReplaySubject } from 'rxjs';

import { RouterHistoryEffects } from './router-history.effects';
import { AddRouteToHistory } from './router-history.actions';
import { ROUTER_NAVIGATED } from '@ngrx/router-store';

describe('RouterHistoryEffects', () => {
 let actions: ReplaySubject<any>;
 let effects: RouterHistoryEffects;

 beforeEach(() => {
   TestBed.configureTestingModule({
     providers: [
       RouterHistoryEffects,
       provideMockActions(() => actions)
     ]
   });

   effects = TestBed.get(RouterHistoryEffects);
 });

 it('should be created', async() => {
   expect(effects).toBeTruthy();
 });

 it('#addRouteToHistory should dispatch fromRouterHistoryActions.AddRouteToHistory with a url', async(async() => {
   actions = new ReplaySubject(1);
   const routerNavigatedAction = {
     type: ROUTER_NAVIGATED,
     payload: {
       routerState: {
         url: '/member'
       }
     }
   };
   actions.next(routerNavigatedAction);
   effects.addRouteToHistory.subscribe((result: AddRouteToHistory) => {
     expect(result.payload).toEqual('/member');
   });
 }));
});

Enter fullscreen mode Exit fullscreen mode

We have a standard operating procedure for creating our testing module except for provideMockActions. This function (which looks like this) accepts an Observable, or a function that returns an Observable. Here, we are creating a factory that generates a ReplaySubject. We can publish to the ReplaySubject stream, and listen to it. This is exactly what our effect does:


 @Effect()
 addRouteToHistory = this.actions.pipe( // do stuff then return an observable )

Enter fullscreen mode Exit fullscreen mode

In our test, we can create a subscription to our effect addRouteToHistory and replay an action that was ‘dispatched’.

   actions = new ReplaySubject(1);
   const routerNavigatedAction = {
     type: ROUTER_NAVIGATED,
     payload: {
       routerState: {
         url: '/member'
       }
     }
   };
   actions.next(routerNavigatedAction);
   effects.addRouteToHistory.subscribe((result: AddRouteToHistory) => {
     expect(result.payload).toEqual('/member');
   });
Enter fullscreen mode Exit fullscreen mode

Hopefully this helps you. Depending if your store obtains slices of state from your store, you may need to create a mock store too! I’ll spare you the details, but it’s very similar to providing mock actions:

//use provideMockStore to provide an instance of MockStore: 
//https://ngrx.io/api/store/testing/MockStore


// configure testing module in before each


import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { provideMockActions } from '@ngrx/effects/testing';

//...
    TestBed.configureTestingModule({
      providers: [
        YourEffect
        provideMockStore({ initialState }),
        provideMockActions(() => actions)
      ]
   })

    mockStore = TestBed.get(Store);
//...


 it('redirect to privacy policy page', async( () => {
// set testing state
   mockStore.setState({ [ROUTER_HISTORY_FEATURE_KEY]: { previousRoutes: [] } });

// dispatch your action
actions = new ReplaySubject(1);
   actions.next({
     type: ROUTER_REQUEST,
     payload: {
       routerState: {
         url: `/${NavigationRoutes.PrivacyPolicy}`
       },
     }
   });
// listen for stream to emit.
   sub = effects.redirectToHome.subscribe((data) => {
    expect(navigationServiceSpy.navigateToPrivacyPolicy).toHaveBeenCalled();
    expect(data).toBeTruthy(); // or whatever you’re testing
   });

 }));


Enter fullscreen mode Exit fullscreen mode

Top comments (0)