Reputation: 2879
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:
selectors1.js
module with jest.mock
, however, it won't work if I'll need to change selectors1.baseSelector
output for some specific casesexport 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?
Upvotes: 28
Views: 25565
Reputation: 5368
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
Upvotes: 0
Reputation: 31
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 { inputsSelector, outputsSelector } from "../store/selectors";
import { mockInputsData, mockOutputsData } from "../utils/test-data";
jest.mock("../store/selectors", () => ({
inputsSelector: jest.fn(),
outputsSelector: jest.fn(),
}));
.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;
});
});
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 ...
});
Upvotes: 3
Reputation: 89
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.
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.
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);
});
});
Upvotes: -1
Reputation: 20162
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:
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)
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.
Upvotes: 45
Reputation: 1169
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
Upvotes: 3