Unit Testing

Unit testing NGXS states is similar to testing other services. To perform a unit test, we need to set up a store with the states against which we want to make assertions. Then, we dispatch actions, listen to changes, and perform expectations.

A basic test looks as follows:

// zoo.state.spec.ts
import { TestBed } from '@angular/core/testing';
import { provideStore } from '@ngxs/store';

import { ZooState } from './zoo.state';
import { FeedAnimals } from './zoo.actions';

describe('Zoo', () => {
  let store: Store;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [provideStore([ZooState])]
    });

    store = TestBed.inject(Store);
  });

  it('it toggles feed', () => {
    store.dispatch(new FeedAnimals());

    const feed = store.selectSnapshot(ZooState.getFeed);
    expect(feed).toBe(true);
  });
});

We recommend using the selectSnapshot or selectSignal methods instead of select or selectOnce, because it would require calling a done function manually. This actually depends on whether states are updated synchronously or asynchronously. If states are updated synchronously, then selectOnce would always emit updated state synchronously.

💡 selectSnapshot may behave similarly to selectSignal, but it would be more readable because you don't need to call the signal function to get the value.

Given the following example:

If you're using Jest, you may use expect.assertions to let Jest know that a certain amount of assertions must run within the test:

The above test would fail if the expectation within the subscribe function isn't run once.

Prepping State

Often in your app, you'll need to test what happens when the state is C and you dispatch action X. You can use store.reset(MyNewState) to prepare the state for your next operation.

⚠️ When resetting the state, ensure you provide the registered state name as the key. store.reset affects your entire state. Merge the current state with your new changes to ensure nothing gets lost.

Testing Selectors

Selectors are simply plain functions that accept the state as an argument, making them easy to test. A simple test might look like this:

In your application you may have selectors created dynamically using the createSelector function:

Testing these selectors is really easy. You just need to mock the state and pass it as a parameter to our selector:

Testing Asynchronous Actions

It's also very easy to test asynchronous actions. You can use async/await along with RxJS's firstValueFrom method, which "converts" Observables to Promises. Alternatively, you can use a done callback.

The example below isn't really complex, but it clearly demonstrates how to test asynchronous code using async/await:

Collecting Actions

Below is the code used to collect actions passing through the actions stream:

The actions collector snippet above was created by the NGXS team and has been successfully used in production apps for years. Now, let's examine an example of how to set up the collector and how to use it:

When using Jest's expect.extend, we can even add our custom matcher to Jest for assertions against dispatched actions:

To make that matcher available in your unit tests, you can provide it in setupFilesAfterEnv:

Here's a simple example of how to use that matcher:

Last updated