How to test a component using react-redux hooks?

ReactjsTestingReduxReact ReduxEnzyme

Reactjs Problem Overview


I have a simple Todo component that utilizes react-redux hooks that I'm testing using enzyme but I'm getting either an error or an empty object with a shallow render as noted below.

What is the correct way to test components using hooks from react-redux?

Todos.js

const Todos = () => {
  const { todos } = useSelector(state => state);

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

Todos.test.js v1

...

it('renders without crashing', () => {
  const wrapper = shallow(<Todos />);
  expect(wrapper).toMatchSnapshot();
});

it('should render a ul', () => {
  const wrapper = shallow(<Todos />);
  expect(wrapper.find('ul').length).toBe(1);
});

v1 Error:

...
Invariant Violation: could not find react-redux context value; 
please ensure the component is wrapped in a <Provider>
...

Todos.test.js v2

...
// imported Provider from react-redux 

it('renders without crashing', () => {
  const wrapper = shallow(
    <Provider store={store}>
      <Todos />
    </Provider>,
  );
  expect(wrapper).toMatchSnapshot();
});

it('should render a ul', () => {
  const wrapper = shallow(<Provider store={store}><Todos /></Provider>);
  expect(wrapper.find('ul').length).toBe(1);
});

v2 tests also fail since wrapper is the <Provider> and calling dive() on wrapper will return the same error as v1.

Thanks in advance for your help!

Reactjs Solutions


Solution 1 - Reactjs

To mock useSelector use can do this

import * as redux from 'react-redux'

const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue({ username:'test' })

Solution 2 - Reactjs

I could test a component which uses redux hooks using enzyme mount facility and providing a mocked store to the Provider:

Component

import React from 'react';
import AppRouter from './Router'
import { useDispatch, useSelector } from 'react-redux'
import StartupActions from './Redux/Startup'
import Startup from './Components/Startup'
import './App.css';

// This is the main component, it includes the router which manages
// routing to different views.
// This is also the right place to declare components which should be
// displayed everywhere, i.e. sockets, services,...
function App () {
  const dispatch = useDispatch()
  const startupComplete = useSelector(state => state.startup.complete)

  if (!startupComplete) {
    setTimeout(() => dispatch(StartupActions.startup()), 1000)
  }

  return (
    <div className="app">
      {startupComplete ? <AppRouter /> : <Startup />}
    </div>
  );
}

export default App;

Test

import React from 'react';
import {Provider} from 'react-redux'
import { mount, shallow } from 'enzyme'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk';
import App from '../App';

const mockStore = configureMockStore([thunk]);

describe('App', () => {
  it('should render a startup component if startup is not complete', () => {
    const store = mockStore({
      startup: { complete: false }
    });
    const wrapper = mount(
      <Provider store={store}>
        <App />
      </Provider>
    )
    expect(wrapper.find('Startup').length).toEqual(1)
  })
})

Solution 3 - Reactjs

There is another way than @abidibo if you use a function selector defined in another file. You can mock useSelector and your selector function, and then use shallow from enzyme:

Component

import * as React from 'react';
import { useSelector } from 'react-redux';
import Spinner from './Spinner';
import Button from './Button ';
import { getIsSpinnerDisplayed } from './selectors';

const Example = () => {
  const isSpinnerDisplayed = useSelector(getIsSpinnerDisplayed);

  return isSpinnerDisplayed ? <Spinner /> : <Button />;
};

export default Example;

Selectors

export const getIsSpinnerDisplayed = state => state.isSpinnerDisplayed;

Test

import * as React from 'react';
import { shallow } from 'enzyme';
import Example from './Example';
import Button from './Button ';
import { getIsSpinnerDisplayed } from './selectors';

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

describe('Example', () => {
  it('should render Button if getIsSpinnerDisplayed returns false', () => {
    getIsSpinnerDisplayed.mockReturnValue(false);

    const wrapper = shallow(<Example />);

    expect(wrapper.find(Button).exists()).toBe(true);
  });
});

It may be a little bit hacky, but it works well for me :)

Solution 4 - Reactjs

Testing React Redux Hooks With Enzyme's Shallow Rendering

After reading through all the responses here and digging through the documentation, I wanted to aggregate the ways to test React components using react-redux hooks with Enzyme and shallow rendering.

These tests rely on mocking the useSelector and useDispatch hooks. I'll also provide examples in both Jest and Sinon.

Basic Jest Example

import React from 'react';
import { shallow } from 'enzyme';
import * as redux from 'react-redux';
import TodoList from './TodoList';

describe('TodoList', () => {
  let spyOnUseSelector;
  let spyOnUseDispatch;
  let mockDispatch;

  beforeEach(() => {
    // Mock useSelector hook
    spyOnUseSelector = jest.spyOn(redux, 'useSelector');
    spyOnUseSelector.mockReturnValue([{ id: 1, text: 'Old Item' }]);

    // Mock useDispatch hook
    spyOnUseDispatch = jest.spyOn(redux, 'useDispatch');
    // Mock dispatch function returned from useDispatch
    mockDispatch = jest.fn();
    spyOnUseDispatch.mockReturnValue(mockDispatch);
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('should render', () => {
    const wrapper = shallow(<TodoList />);

    expect(wrapper.exists()).toBe(true);
  });

  it('should add a new todo item', () => {
    const wrapper = shallow(<TodoList />);

    // Logic to dispatch 'todoAdded' action

    expect(mockDispatch.mock.calls[0][0]).toEqual({
      type: 'todoAdded',
      payload: 'New Item'
    });
  });
});

Basic Sinon Example

import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import * as redux from 'react-redux';
import TodoList from './TodoList';

describe('TodoList', () => {
  let useSelectorStub;
  let useDispatchStub;
  let dispatchSpy;  

  beforeEach(() => {
    // Mock useSelector hook
    useSelectorStub = sinon.stub(redux, 'useSelector');
    useSelectorStub.returns([{ id: 1, text: 'Old Item' }]);

    // Mock useDispatch hook
    useDispatchStub = sinon.stub(redux, 'useDispatch');
    // Mock dispatch function returned from useDispatch
    dispatchSpy = sinon.spy();
    useDispatchStub.returns(dispatchSpy);
  });

  afterEach(() => {
    sinon.restore();
  });
  
  // More testing logic...
});
Testing Multiple useSelector Hooks

Testing multiple useSelectors requires us to mock the Redux app state.

var mockState = {
  todos: [{ id: 1, text: 'Old Item' }]
};

Then we can mock our own implementation of useSelector.

// Jest
const spyOnUseSelector = jest.spyOn(redux, 'useSelector').mockImplementation(cb => cb(mockState));

// Sinon
const useSelectorStub = sinon.stub(redux, 'useSelector').callsFake(cb => cb(mockState));

Solution 5 - Reactjs

I think this is both the best and the simplest way to mock useSelector hook from Redux store in jest:

  import * as redux from 'react-redux'

  const user = {
    id: 1,
    name: 'User',
  }

  const state = { user }

  jest
    .spyOn(redux, 'useSelector')
    .mockImplementation((callback) => callback(state))

With the idea being that you can only mock a subset of your store in the state const.

Solution 6 - Reactjs

After searching for help I combined some of the methods I found to mock useSelector.

First create a function that does some bootstrapping before your test. Setting up the store with some values that you want to overwrite and mock the useSelector function of react-redux.

I think it is really useful for creating multiple testcases where u see how the store state influences the behaviour of your component.

import configureMockStore from 'redux-mock-store';
import * as Redux from 'react-redux';
import MyComponent from './MyComponent';

const mockSelectors = (storeValues) => {
  const mockStore = configureMockStore()({
    mobile: {
      isUserConnected: false
      ...storeValues
    },
  });

  jest
    .spyOn(Redux, 'useSelector')
    .mockImplementation(state => state.dependencies[0](mockStore.getState()));
};

describe('isUserConnected: true', () => {
    beforeEach(() => {
      mockSelectors({ isUserConnected: true });
      component = shallow(<MyComponent />);

      test('should render a disconnect button', () => {
         expect(component).toBeDefined();
         expect(component.find('button')).toBeTruthy();
      });
    });
  });

And the component:

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

const MyComponent = () => {    
  const isConnected = useSelector(selectIsConnected, shallowEqual);

  return (
    <>
      {
        showDisconnect ? (
          <button type="button" onClick={() => ()}>disconnect</button>
        ) : null
      }
    </>
  );
};

export default MyComponent;

Solution 7 - Reactjs

Below code works for me.

import { configure, shallow} from 'enzyme'; 
import Adapter from 'enzyme-adapter-react-16'; 
import ServiceListingScreen  from './ServiceListingScreen'; 
import renderer from 'react-test-renderer';
import { createStore } from 'redux';
import serviceReducer from './../store/reducers/services'; 
import { Provider } from 'react-redux'
 
const store = createStore(serviceReducer  ) ; 
configure({adapter: new Adapter()}); 


const ReduxProvider = ({ children, reduxStore }) => (
  <Provider store={reduxStore}>{children}</Provider>
)

describe('Screen/ServiceListingScreen', () => {
  it('renders correctly ', () => {
    const wrapper = shallow(<Provider store={store}><ServiceListingScreen  /></Provider>);
    const tree = renderer.create(wrapper).toJSON();
    expect(tree).toMatchSnapshot();
  });
   
});

Solution 8 - Reactjs

You can try redux-saga-test-plan cool redux assertion and running library and lightweight it doest all your heavy lifting of running saga and assertion automatically

const mockState = { rick:"Genius", morty:"dumbAsss"}

expectSaga(yourCoolSaga)
      .provide({
        select({ selector }, next) {
          if (selector) {
            return mockState;
          }
          return next();
        }
      })
    // some assertion here
      .put(actions.updateRickMortyPowers(mockState))
      .silentRun();

Solution 9 - Reactjs

this is worked for me as well :

import { shallow, mount } from "enzyme";
const store = mockStore({
  startup: { complete: false }
});

describe("==== Testing App ======", () => {
  const setUpFn = props => {
    return mount(
      <Provider store={store}>
        <App />
      </Provider>
    );
  };

  let wrapper;
  beforeEach(() => {
    wrapper = setUpFn();
  });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questioncrowns4daysView Question on Stackoverflow
Solution 1 - ReactjsKritiView Answer on Stackoverflow
Solution 2 - ReactjsabidiboView Answer on Stackoverflow
Solution 3 - ReactjsjhujhulView Answer on Stackoverflow
Solution 4 - ReactjsAndrew WView Answer on Stackoverflow
Solution 5 - ReactjsPaweł GościckiView Answer on Stackoverflow
Solution 6 - ReactjsGertjan VermeirView Answer on Stackoverflow
Solution 7 - ReactjsAmit Kumar TrivediView Answer on Stackoverflow
Solution 8 - ReactjsAshif ZafarView Answer on Stackoverflow
Solution 9 - ReactjsHilal AissaniView Answer on Stackoverflow