The Complete Guide to NgRx Testing (2020)

Share on facebook
Share on google
Share on twitter
Share on linkedin

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 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.

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:

You normally start with testing the default case:

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:

Note that I’m using the 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:

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:

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

Testing Effects

Testing effects become slightly more complicated as you here need to assert a reactive result as 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:

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

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:

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:

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:

You would test it like this:

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:

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 using override/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. In this class, is where all the NgRx delegation will reside (dispatch actions and setup selectors).

I recommend you use MockStore (with/without overrideSelector) to mocking the store state.

MockStore

If you want to involve the selectors in the test (integtration testing them), you should use MockStore:

You set the state using the setState method from MockStore.

This is the complete example:

OverrideSelector

If you don’t need to include the selectors in the tests, you can enjoy, faster and easier tests by using overrideSelector.

With overrideSelector you can simply specify a return value for a specific selector, so you don’t need to set the store state using MockStore.

You just use it like:

mockStore.overrideSelector(fromAuth.getUsername, 'John');

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 for reducing boilerplate with selectors and actions, you can use to make it more simple and frictionless to work with NgRx.

Do you want to become an Angular architect? Check out Angular Architect Accelerator.

Related Posts and Comments

New Angular project? This is how I would start

Throughout my career as an Angular consultant, I have had occasions where I had to start with an Angular project from scratch which is a pipe dream for every software developer. Do everything the right way at the get-go, no legacy, you follow me… So, how do you get started with the project from scratch,

Read More »

Error, loading, content…? Use this page pattern for your Angular apps

When developing Angular applications, it’s common for pages to transition through three key states: error, loading, and show content. Every time you fetch data from an API, your page will likely show a loading indicator first, and then either render the content successfully or display an error message if something goes wrong. This pattern is

Read More »

How to do Cypress component testing for Angular apps with MSW

In this post, we will cover how to do Cypress Component testing with MSW (mock service worker) and why it’s beneficial to have a mock environment with MSW. The mock environment My recommendation for most enterprise projects is to have a mocking environment as it serves the following purposes : * The front end can

Read More »

6 thoughts on “The Complete Guide to NgRx Testing (2020)”

  1. Ohoy Christian

    I got a question regarding the selectors.

    You mention reducers as pure functions, but so are selectors?
    in ngrx you can easily test selectors with the .projector() function.

    In your example, you´re testing your class TodoListSelector, which is a facade / layer on top of the ngrx selectors. But you´re not really testing the selector itself.

    Also, if you hadn´t seen, ngrx introduced the mockStore with version 7, so testing components / classes interacting with the store becomes a lot easier!

    Nonetheless, nice reading about more Angular testing!

    1. Christian Lydemann

      Hi Poul,

      Thank you for that. You are right about the selector functions itself should be tested in isolation. I have now updated the post with a selector test that uses projector to test in isolation.
      It is nice with the mockStore compared to stubbing the whole store for every test. The benefits with using these facades is that you don’t need to deal with the mockStore at all, which I find eliminates the most boilerplate.

      1. Awesome with the update!

        I agree with the facades, and have used something similar in projects too. But seeing as you´d need/want to test those anyways, the mockStore becomes pretty valuable in things like integration tests.

        I´d love to talk more testing if you´re free one day

  2. Thank you Christian. After having searched the web for over a day, I came across your post on how to test angular ngrx effects and I could finally understand how to use the jasmine marbles library to test ngrx effects.

  3. How do we test selectors with props?


    const someSelector = (props: string) => createSelector(
    someRootSelector,
    (state) => {
    // some computation with state using props
    }
    );

    store.select(someSelector('hello')).subscribe(...);

    Here just using `overriderSelector` is not sufficient as it is resulting in undefined. Any recommendations on how to test this scenario?

Leave a Comment

Your email address will not be published. Required fields are marked *