Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 850
Eugene Tsakh Avatar asked Apr 15 '19 20:04

Eugene Tsakh


1 Answers

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.

like image 66
Maciej Sikora Avatar answered Sep 30 '22 16:09

Maciej Sikora