Unit Testing NgRx Effects
By: Josh Hicks
Tags: Ngrx, Angular, JavaScript, Testing, Unit Testing

Example 1: Testing Effects That Use Services

Interacting with APIs is one of the most common things you will be doing in any serious application. That’s why I thought it was so important to zoom in on this one first. Keep in mind this test would work with any HTTP verb.

In order to safely test a service that uses HTTP it’s generally a best practice to use a mock replacement of the actual service. Here is the small mock service we will use.

class MockUserService {

fetchUsers() {

return of(mockUsers);

}

}

Now we can spy on that method to make sure it has been called.

// Testing API interaction

describe('onFetchUsers$', () => {

it('should fire if users is null', (done) => {

const spy = spyOn(httpService, 'fetchUsers').and.callThrough();

actions$ = of(AppActions.fetchUsers);

effects.onFetchUsers$.subscribe((res) => {

expect(res).toEqual(AppActions.fetchUsersSuccess({ data: mockUsers }));

expect(spy).toHaveBeenCalledTimes(1);

done();

});

});

});

Can you see the 4 steps I listed earlier? We are creating the new mock action, calling subscribe, expecting a result, and calling the done function. Boom! No complicated marbles, no new syntax to learn, just plain old subscribe/expect.

This is why I recommend this method for most teams. This will help uhh… “flatten the curve” of learning. The main thing to test in this case is that the method that wraps the HTTP call in the service was called. I don’t care if the HTTP service was actually called here because that should be covered in the service unit tests.

Example 2: Testing Effects That Use The Router-Store

Using the router state to do things in your app is a really cool concept. There are so many possible ways to use it. Luckily, testing your interaction with it is also pretty easy. All you need to do is stick to the 4 steps.

// Testing interaction with Router-Store

describe('onSignOut$', () => {

it('should dispatch userSignOut if route matches', (done) => {

actions$ = of({

type: ROUTER_NAVIGATION,

payload: {

routerState: {

url: 'signout',

},

},

});

effects.onSignOut$.subscribe((action) => {

expect(action).toEqual(AppActions.userSignOut());

done();

});

});

});

This is an example of creating a mock payload. We know if the effect will check that payload in order to know when to fire like so:

filter((action) => action.payload.routerState.url.includes(‘signout’))

Now when the effect looks for that payload it will find the mock one we added!

Example 3: Testing Effects That Don’t Dispatch Actions

Sometimes you want to perform a side-effect but, you don’t need to dispatch any other actions after you’re done. Adding something to localStorage is a perfect example of that.

// Testing non-dispatching side-effect

describe('onEnroll$', () => {

it('should set item in local storage', (done) => {

const spy = spyOn(localStorage, 'setItem').and.callFake(() => true); actions$ = of(AppActions.userEnroll());

effects.onEnroll$.subscribe((action) => {

expect(action).toEqual(AppActions.userEnroll());

expect(spy).toHaveBeenCalled();

done();

});

});

});

Here we are doing something that is very similar to the API effect we tested earlier. Which is great! You can use the same technique for more than one situation. Just make sure to create that fake method or create a mock some other way.

As you can see, we are once again creating a spy for the localStorage method, and then making sure it was called. Notice that the action that is returned is the original action that caused the effect to trigger? That’s because we’re not dispatching a new action, and we can now verify that is the case.

Summary

In this post I tried to zoom in on some of the most complicated pieces of your NgRx app to show you that testing them isn’t as scary as you think. I still think that writing unit tests sucks, and that’s okay. I don’t have to like every aspect of programming in order to love it as a whole. As long as you understand how important testing is, then the pain will be much less. Then when you add in testing with technology you already know and understand, like observables, things get even easier!

Follow the 4 steps!

Did you learn anything? Do you hate unit testing? Do you love it? Are you one of the Marble Testing evangelists? Let me know in the comments!

Source Code:

https://github.com/Josh-Hicks/ngrx-examples