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

5

28

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?

Sweetener answered 15/4, 2019 at 20:8 Comment(1)
If I'm understanding this correctly, selectors2 is creating a new instance of selectors1 therefore it's not the instance you mocked with the spy method but you would like it to be? One option may be to to use dependency injection so you can initialize it with the mocked instance. Another may be to mock selector2's target selector to return a mocked instance1, but it sounds like you're trying to avoid that for scalability reasons. I feel like I may not be grasping the whole domain of the problem or maybe exactly what createSelector is doing. You've already touched on using a module mock.Runck
A
45

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.

Abc answered 17/4, 2019 at 21:28 Comment(4)
Yes, this is exactly what I wanted to achieve. Thank you for your help!Sweetener
It seems that you should be passing to resultFunc as many arguments as there are input selectors in your selector, instead of a collection, as a single argument. So instead of selector2.resultFunc([returnOfSelector1Mock]); it should be selector2.resultFunc(returnOfSelector1Mock);Popham
@Popham I don't think it is any problem with having or not having []Abc
You deserve an award, how did I miss this after all these years.Intrigant
R
3

You could achieve this by mocking the entire selectors1.js module, but also importing is inside test, in order to have access over the mocked functionality

Assuming your selectors1.js looks like

import { createSelector } from 'reselect';

// selector
const getFoo = state => state.foo;

// reselect function
export const baseSelector = createSelector(
  [getFoo],
  foo => foo
);

and selectors2.js looks like

import { createSelector } from 'reselect';
import selectors1 from './selectors1';

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

Then you could write some test like

import { baseSelector } from './selectors1';
import { targetSelector } from './selectors2';

// This mocking call will be hoisted to the top (before import)
jest.mock('./selectors1', () => ({
  baseSelector: jest.fn()
}));

describe('selectors', () => {
  test('foo.a = 1', () => {
    const state = {
      foo: {
        a: 1
      }
    };
    baseSelector.mockReturnValue({ a: 1 });
    expect(targetSelector(state)).toBe(1);
  });

  test('foo.a = 2', () => {
    const state = {
      foo: {
        a: 1
      }
    };
    baseSelector.mockReturnValue({ a: 2 });
    expect(targetSelector(state)).toBe(2);
  });
});

jest.mock function call will be hoisted to the top of the module to mock the selectors1.js module When you import/require selectors1.js, you will get the mocked version of baseSelector which you can control its behaviour before running the test

Rabiah answered 17/4, 2019 at 20:58 Comment(6)
Hm. That's an interesting solution, however I'd consider Maciej Sikora's answer as the best one. The only thing I would add - put jest.mock entry in beforeEach, otherwise your mock won't be reset before each test.Sweetener
I'm happy you found the answer you are looking for. However, you cannot put jest.mock entry in beforeEach, because as I mentioned, jest will hoist this call to the top of the module. In order to reset the mock before each test, you simply do baseSelector.mockClear or baseSelector.mockReset depending on what you want to achieveRabiah
Hm. This is not really good solution either, because some files might have dozens of selectors. However, jest.resetModules() in beforeEach should do the trickSweetener
That's why I don't like jest mocking functionality. I like sinon with proxyquire moreRabiah
I agree that there might be better solutions for mocking, but in this particular case, as it turned out, there is an ability to test selectors without mocking. Also, Jest is not so bad. It's fast and pretty flexible framework, but yes, mocking could be done better. :)Sweetener
I couldn't agree more. You know, there is always something for improvement ;)Rabiah
G
3

For anyone trying to solve this using Typescript, this post is what finally worked for me: https://dev.to/terabaud/testing-with-jest-and-typescript-the-tricky-parts-1gnc

My problem was that I was testing a module that called several different selectors in the process of creating a request, and the redux-mock-store state that I created wasn't visible to the selectors when the tests executed. I ended up skipping the mock store altogether, and instead I mocked the return data for the specific selectors that were called.

The process is this:

  • Import the selectors and register them as jest functions:
    import { inputsSelector, outputsSelector } from "../store/selectors";         
    import { mockInputsData, mockOutputsData } from "../utils/test-data";

    jest.mock("../store/selectors", () => ({
      inputsSelector: jest.fn(),
      outputsSelector: jest.fn(),
    }));
  • Then use .mockImplementation along with he mocked helper from ts-jest\utils, which lets you wrap each selector and give custom return data to each one.
    beforeEach(() => {
        mocked(inputsSelector).mockImplementation(() => {
            return mockInputsData;
        });
        mocked(outputsSelector).mockImplementation(() => {
            return mockOutputsData;
        });
    });
  • If you need to override the default return value for a selector in a specific test, you can do that inside the test() definition like this:
    test("returns empty list when output data is missing", () => {
        mocked(outputsSelector).mockClear();
        mocked(outputsSelector).mockImplementationOnce(() => {
            return [];
        });
        // ... rest of your test code follows ...
    });
Guardianship answered 11/6, 2020 at 15:13 Comment(0)
L
0

In addition to the answer of @nate-peters I would like to share an even easier version:

import { baseSelector } from "../store/selectors";         

jest.mock("../store/selectors", () => ({
  baseSelector: jest.fn(),
}));

Then in every it(...) block you can implement

it('returns ...', () => {
    baseSelector.mockReturnValueOnce(values);
    //...rest of you code

Longford answered 21/12, 2023 at 10:58 Comment(0)
F
-1

I ran into the same problem. I ended up mocking the reselect createSelector with jest.mock to ignore all but the last argument (which is the core function you want to test) when it comes to testing. Overall this approach has served me well.

Problem I had

I had a circular dependency within my selector modules. Our code base is too large and we don't have the bandwidth to refactor them accordingly.

Why I used this approach?

Our code base has a lot of circular dependencies in the Selectors. And trying to rewrite and refactor them to not have circular dependencies are too much work. Therefore I chose to mock createSelector so that I don't have to spend time refactoring.

If your code base for selectors are clean and free of dependencies, definitely look into using reselect resultFunc. More documentation here: https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc

Code I used to mock the createSelector

// Mock the reselect
// This mocking call will be hoisted to the top (before import)
jest.mock('reselect', () => ({
  createSelector: (...params) => params[params.length - 1]
}));

Then to access the created selector, I had something like this

const myFunc = TargetSelector.IsCurrentPhaseDraft;

The entire test suite code in action

// Mock the reselect
// This mocking call will be hoisted to the top (before import)
jest.mock('reselect', () => ({
  createSelector: (...params) => params[params.length - 1]
}));


import * as TargetSelector from './TicketFormStateSelectors';
import { FILTER_VALUES } from '../../AppConstant';

describe('TicketFormStateSelectors.IsCurrentPhaseDraft', () => {
  const myFunc = TargetSelector.IsCurrentPhaseDraft;

  it('Yes Scenario', () => {
    expect(myFunc(FILTER_VALUES.PHASE_DRAFT)).toEqual(true);
  });

  it('No Scenario', () => {
    expect(myFunc(FILTER_VALUES.PHASE_CLOSED)).toEqual(false);
    expect(myFunc('')).toEqual(false);
  });
});
Florentinaflorentine answered 16/8, 2019 at 14:34 Comment(2)
Why do you need to mock createSelector if you can use selector's resultFunc method?Sweetener
Our codebase has a lot of circular dependencies which makes the imports null. Using the above approach helps with keeping the code as is and not touching it. I don't disagree that using resultFunc is a better approach here. But if you have no choice when it comes to refactoring, then my approach makes sense.Florentinaflorentine

© 2022 - 2025 — McMap. All rights reserved.