67
votes

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!

9
Did you ever figure this out? I'm running into the same issue after migrating to Redux hooks.Markus Hay
It appears to be an issue with Enzyme specifically, but so far there don't appear to be any adequate workarounds using the shallow renderer. Better hooks support should be in the next Enzyme release: github.com/airbnb/enzyme/issues/2011Markus Hay
Use mount instead of shallow, as shallow only renders the root component and places the custom child components as-isShivang Gupta

9 Answers

45
votes

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)
  })
})
43
votes

To mock useSelector use can do this

import * as redux from 'react-redux'

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

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 :)

7
votes

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));
2
votes

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();
  });
   
});

1
votes

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;
0
votes

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.

0
votes

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();
-3
votes

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();
  });