Is there a way to properly mock Reselect selectors for unit testing?

Eugene Tsakh picture Eugene Tsakh · Apr 15, 2019 · Viewed 10.3k times · Source

I'm having a pretty complex selectors structure in my project (some selectors may have up to 5 levels of nesting) so some of them are very hard to test with passing input state and I would like to mock input selectors instead. However I found that this is not really possible.

Here is the most simple example:

// selectors1.js
export const baseSelector = createSelector(...);

-

// selectors2.js
export const targetSelector = createSelector([selectors1.baseSelector], () => {...});

What I would like to have in my test suite:

beforeEach(() => {
  jest.spyOn(selectors1, 'baseSelector').mockReturnValue('some value');
});

test('My test', () => {
  expect(selectors2.targetSelector()).toEqual('some value');
});

But, this approach won't work as targetSelector is getting reference to selectors1.baseSelector during initialization of selectors2.js and mock is assigned to selectors1.baseSelector after it.

There are 2 working solutions I see now:

  1. Mock entire selectors1.js module with jest.mock, however, it won't work if I'll need to change selectors1.baseSelector output for some specific cases
  2. Wrap every dependency selectors like this:

export const targetSelector = createSelector([(state) => selectors1.baseSelector(state)], () => {...});

But I don't like this approach a lot for obvious reasons.

So, the question is next: is there any chance to mock Reselect selectors properly for unit testing?

Answer

Maciej Sikora picture Maciej Sikora · Apr 17, 2019

The thing is that Reselect is based on the composition concept. So you create one selector from many others. What really you need to test is not the whole selector, but the last function which do the job. If not, the tests will duplicate each other, as if you have tests for selector1, and selector1 is used in selector2, then automatically you test both of them in selector2 tests.

In order to achieve:

  • less mocks
  • no need to specially mock result of composed selectors
  • no test duplication

test only the result function of the selector. It is accessible by selector.resultFunc.

So for example:

const selector2 = createSelector(selector1, (data) => ...);

// tests

const actual = selector2.resultFunc([returnOfSelector1Mock]);
const expected = [what we expect];
expect(actual).toEqual(expected)

Summary

Instead of testing the whole composition, and duplicating the same assertion, or mocking specific selectors outputs, we test the function which defines our selector, so the last argument in createSelector, accessible by resultFunc key.