mock useSelector of react-redux with jest for test with react testing-library
Asked Answered
R

2

5

I'm trying to test a component that uses react-redux for state management.

To test quickly I want to mock useSelector, like this:

const templates = [
  {
    id: ...,
    name: ...,
    ...
  },
  {
    id: ...,
    name: ...,
    ...
  },
]


jest.mock('react-redux', () => ({
  ...jest.requireActual('react-redux'),
  useSelector: jest.fn(),
}));


describe('some awesome description', () => {
  beforeEach(() => {
    useSelector.mockImplementation(callback => callback(templates));
  });
});

But when running the tests, this fails as follows:

TypeError: Cannot read properties of undefined (reading 'ids')

  141 | describe('layouts/TemplatesPanel', () => {
  142 |   beforeEach(() => {
> 143 |     useSelector.mockImplementation(callback => callback(templates));
      |                                                ^
Redhanded answered 26/8, 2022 at 0:18 Comment(0)
A
8

You'd better not mock useSelector, mock implementation possibly breaks its function because its function is not only to return a certain state slice. See the useSelector real implementation, it doesn't just return the selectedState.

The recommended way is to create a mock store and provide mock data for it.

E.g.

index.tsx:

import React from 'react';
import { useSelector } from 'react-redux';

export type Template = {
  id: string;
  name: string;
};
export type RootState = {
  templates: Template[];
};

export const MyComp = () => {
  const templates = useSelector<RootState>((state) => state.templates);
  console.log('templates: ', templates);
  return <div>MyComp</div>;
};

index.test.tsx:

import { render } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { MyComp, RootState, Template } from '.';

describe('73494842', () => {
  test('should pass', () => {
    const templates: Template[] = [
      { id: '1', name: 'a' },
      { id: '2', name: 'b' },
    ];
    const mockStore = createStore<RootState, any, any, any>((state = { templates }, action) => {
      if (action.type === 'UPATE_NAME') {
        return {
          ...state,
          templates: templates.map((t) => (t.id === action.payload.id ? { ...t, name: action.payload.name } : t)),
        };
      }
      return state;
    });
    render(
      <Provider store={mockStore}>
        <MyComp />
      </Provider>
    );
    mockStore.dispatch({ type: 'UPATE_NAME', payload: { id: '1', name: 'c' } });
  });
});

Test result:

  73494842
    ✓ should pass (44 ms)

  console.log
    templates:  [ { id: '1', name: 'a' }, { id: '2', name: 'b' } ]

      at MyComp (stackoverflow/73494842/index.tsx:14:11)

  console.log
    templates:  [ { id: '1', name: 'c' }, { id: '2', name: 'b' } ]

      at MyComp (stackoverflow/73494842/index.tsx:14:11)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.931 s, estimated 11 s

When we dispatch an action later, the useSelector hook will subscribe to the changes of the store, it will execute again and get the updated state slice. If you mock it just return a state slice, this feature is not working anymore.

Aleksandrovsk answered 26/8, 2022 at 2:23 Comment(0)
E
3

You need to pass the callback function inside the jest.mock too.

useSelector: (callback) => jest.fn(callback)
Exhort answered 7/3 at 14:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.