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.
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
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
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.
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 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 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:
- You can create either a cold or a hot observable with Marbles. These are created with the functions:
- “-” 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
- “|” means completed observable
- “#” means error, you can specify the error by setting it as the third argument to
- “()” can wrap a couple of events that should happen on the same timeframe
- [a-z 0-9] Everything else is variables, that can be set with the second argument in
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
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)
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).
If you want to involve the selectors in the test (integtration testing them), you should use
You set the state using the
setState method from
This is the complete example:
If you don’t need to include the selectors in the tests, you can enjoy, faster and easier tests by using
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:
Complete Demo Repository
You can find a complete demo of NgRx testing a Todo app on my Github.
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.