'Mock ngrx store selectors with parameters in unit tests (Angular)

I am trying to write unit tests for a service in Angular. I want to mock the store.select function of ngrx, so I can test how let's say, a service, reacts to different values returned by the store selectors. I want to be able to mock each selector individually.

My main problem is how to mock parametrised selectors.

I have previously used a BehaviourSubject that I map to the select function, but this doesn't allow you to return different values for different selectors. It is not readable because it is not obvious what selector you are mocking.

Opt 1: Mock Store using subject: impossible to know which selector the subject corresponds to, can't return different values for different selectors.

// service.spec.ts

const selectSubject = new BehaviourSubject(null);
class MockStore {
    select = () => selectSubject;
}

Opt 2: Mock Store using switch: works for different selectors, but cannot make it work when the selectors have parameters.

// service.spec.ts

    // This works well but how can I make it work with selectors with parameters??
    const firstSubject = new BehaviourSubject(null);
    const secondSubject = new BehaviourSubject(null);
    class MockStore {
        select = (selector) => {
            switch (selector): {
                case FirstSelector: {
                    return firstSubject;
                }
                case SecondSelector: {
                    return secondSubject;
                }
             }
        };
    }

    describe('TestService', () => {
        let service: TestService;
        let store: Store<any>;

        beforeEach(() => {
            TestBed.configureTestingModule({
              providers: [
                TestService,
                { provide: Store, useClass: MockStore }
              ],
            });

            service = TestBed.get(TestService);
            store = TestBed.get(Store);
          });

          it('should do X when first selector returns A and second selector returns B', () => {
            firstSelectorSubject.next(A);
            secondSelectorSubject.next(B);

        // Write expectation
          });
    });

Service method with parametrized selector I want to mock, so I can test getUserName with different id values

getUserName(id: string): Observable<string> {
    return this.store.select(getUser(id)).pipe(
      filter(user => user !== null),
      map(user => user.fullName)
    );
  }


Solution 1:[1]

I've been working through a similar issue for awhile now and think i've found a way to make it work.

With the selector of

export const getItemsByProperty = (property: string, value: any) => createSelector(getAllItems, (items: ItemObj[]) => items.filter((item) => item[property] == value));

and where

export const getAllItems = createSelector(getState, (state) => selectAll(state.items));

in my components unit test file i override the selector for the getItemsByProperty's underlying selector call, getAllItems, with data and then expect the filtered data in my tests. If what you want to return changes, then just update the result of getAllItems.

Solution 2:[2]

Why overrideSelector does not work

The store method overrideSelector from @ngrx/store/testing works great for selectors without parameters but does not work for mocking parameterised/factory selectors like this one:

const getItem = (itemId) => createSelector(
  getItems,
  (items) => items[itemId]
);

A new function gets created for every call to the factory function so the test class and the real class will create two separate functions and thus overrideSelector will not be able to match the functions calls.

Use spy methods

To mock factory selectors we can instead use spy methods in test frameworks like jest or jasmine.

Code example for jest:

import * as ItemSelectors from '../selectors/item.selectors';

...

const mockItem = { someProperty: 1 };

jest.spyOn(ItemSelectors, 'getItem').mockReturnValue(
  createSelector(
    (v) => v,
    () => mockItem
  )
);

For Jasmine the corresponding spy call would be something like:

  spyOn(ItemSelectors, 'getItem').and.returnValue(...);

Memoize the factory function

A different approach could be to memoize the factory function (ie getItem) so that the same function will always be returned for the same input arguments (e.g. by using memoize in lodash). Then it will be possible to use overrideSelector. However, be aware of that this builds a cache which continues to grow every time getItem gets called which can cause memory related performance problems.

Solution 3:[3]

NgRx 7.0 includes @ngrx/store/testing for mocking Store. There is an overrideSelector method that is very handy. You are basically mocking the output of the selector, so the parameters wouldn't matter.

https://medium.com/ngconf/mockstore-in-ngrx-v7-0-f7803708de4e

mockStore.overrideSelector(yourSelector, expectedOutput);

You can also set up the selector in the initialization of the MockStore:

const mockStore = new MockStore<fromState.IState>(
    new MockState(),
    new ActionsSubject(),
    null,
    null,
    [{
        selector: yourSelector,
        value: expectedOutput
    }]
);

If you want to actually test the selector, you should have a test file specifically for the selector. To test a parameterized selector, you will use the projector method. It takes in the slice of state (or object) that the selector acts on and any parameters. In my example, I am testing an NgRx entity.

selector.ts:

export const getEntityById: any = createSelector(
    getEntitiesAsDictionary,
    (entities, props) => entities[props.id]
);

spec.ts:

const expectedId = 111;
const expected = { id: expectedId , user: { userId: expectedId } };

const iEntities = { 
    [expectedId ]: expected, 
    [222]: { id: 222, user: { userId: 222 }}, 
    [333]: { id: 333, user: { userId:333 }}
};

const actual = fromState.getEntityById.projector(iEntities, { id: expectedId });

expect(actual).toEqual(expected);

Solution 4:[4]

Solution provided by @Tobias Lindgren worked for me. Extending his solution to give example to people looking here for Jasmine with Karma approach:

// spec file -> I have used this snippet in `beforeEach` block
spyOn(Selectors, 'getContent').and.returnValue(
  createSelector(
    // (v: any) => v, <- this is required if you're on NgRx 12, for 13 you can omit this line
    () => mockValue
  )
);

// this is how my selector looked like & I am using NgRx v13
const getContent = (props: string) => createSelector(
  someRootSelector,
  (stateFromRoot) => {
    return {
      // some new configuration by combining stateFromRoot & props
    }
  } 
);

export const Selectors = {
  getContent,
  many other selectors
};

Solution 5:[5]

This answer by A Hutch worked for me.

However in 2022 there were a few extra syntactical considerations needed, most importantly tearing down after each run.

Here is an explicit example based on the original question, where the store is based on 'user' entities and the ID in question is 1:

TestBed.configureTestingModule({
  teardown: { destroyAfterEach: false }, // needed when using provideMockStore.
  providers: [
    TestService,
    provideMockStore({
      selectors: [
        {
          selector: getEntities,
          value: { [1]: { fullName: 'John Smith' } },
        },
      ],
    }),
  ],
});

Example test:

it('should get the full name', () => {
  const result = getUserName(1);
    
  expect(result).toEqual('John Smith');
});

Example selector

export const getUserName = (userId: number) => createSelector(getEntities, entities => entities[userId]?.fullName);

Solution 6:[6]

Actually mock store doesn't need for testing selectors, but sure it can be done with mock store as well. It's enough to define an adapter from your entity:

  let adapter: EntityAdapter<MetaDataEntity>;
  let state: fromReducer.State;

  beforeAll(() => {
    adapter = createEntityAdapter<MetaDataEntity>();
    state = adapter.getInitialState(fromReducer.initialState);
  });

Later it's easy to test any selector:

  describe('getSelected', () => {
    it('should return the selected entity state', () => {
      let localState = adapter.setAll([mockMetaDataEntity], {
        ...state,
        selectedId: mockMetaDataEntity.id
      });
      expect(fromSelectors.getSelected(localState)).toEqual(mockMetaDataEntity);
    });
  });

And with parametered selector:

export const getMetaData = (metaDataId: string) =>
  createSelector(getMetaDataEntities, (entities) => entities[metaDataId]);

and the test:

  describe('getMetaData', () => {
    it('should return the meta data entity by id', () => {
      let localState = adapter.setAll([mockMetaDataEntity], {
        ...state,
        selectedId: 'any other id'
      });
      const selectorFunc = fromSelectors.getMetaData(mockMetaDataEntity.id);
      expect(selectorFunc(localState)).toEqual(mockMetaDataEntity);
    });
  });

This is the key - create a local function from the parameterized selector and call it with the store:

const selectorFunc = fromSelectors.getMetaData(mockMetaDataEntity.id); expect(selectorFunc(localState)).toEqual(mockMetaDataEntity);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 A Hutch
Solution 2 Tobias Lindgren
Solution 3
Solution 4
Solution 5
Solution 6 gsziszi