Dean Nelson
Dean Nelson

Reputation: 31

How to shallow test a non redux connected component that uses redux hooks

I have a problem with my unit tests in jest/enzyme in my ReactJS project, I converted my components to ES6 and to use hooks for my redux connection to the reducer store. However after adding the change to using hooks in this component the unit test for it has been broke and I have spent a few days trying to figure out why this test will no longer shallow render as a full mount will work fine.

DemoPage.js

import React  from 'react';
import { useSelector, useDispatch} from 'react-redux';
import * as actions from '../../actions/demoPageActions';
import DemoPageForm from '../demoApp/DemoPageForm';
import {compose} from "recompose";
import {withStyles} from "@material-ui/core";

const styles = theme => ({});

export const DemoPage = () => {
  const demoState = useSelector(state => state.demoPage);
  const dispatch = useDispatch();

  const saveSomething = () => {
    dispatch(actions.saveSomething(demoState));
  };

  const calculateSomething = e => {
    dispatch(actions.calculateSomething(demoState, e.target.name, e.target.value));
  };

  return (
    <DemoPageForm
      onSaveClick={saveSomething}
      onChange={calculateSomething}
      demoState={demoState}
    />
  );
};

export default compose(withStyles(styles))(DemoPage);

DemoPage.spec.js

import React from "react";
import { shallow } from "enzyme";
import {DemoPage} from "./DemoPage";
import DemoPageForm from "../demoApp/DemoPageForm";

describe("<DemoPage />", () => {
  it("should contain <DemoPageForm />", () => {
    const wrapper = shallow(
      <DemoPage/>
    );
    expect(wrapper.find(DemoPageForm).length).toEqual(1);
  });
});

Which produces the following error

 <DemoPage /> › should contain <DemoPageForm />

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

       9 | 
      10 | export const DemoPage = () => {
    > 11 |   const demoState = useSelector(state => state.demoPage);
         |                     ^
      12 |   const dispatch = useDispatch();
      13 | 
      14 |   const saveSomething = () => {

This is highly confusing as it should still be testing the unconnected component however I believe the hooks for useSelector are causing this issue so after reading about 30-40 pages worth of content about these I have yet to find a soltuion and the closest I have got is using mount which works fine however I would prefer shallow for this testing, here is the code and result when wrapped in a provider with a mockstore

describe("<DemoPage />", () => {
  const mockStore = configureMockStore()
  const store = mockStore(returnInitialState());

  it("should contain <DemoPageForm />", () => {
    const wrapper = shallow(
      <Provider store={store}>
        <DemoPage
          store={store}
        />
      </Provider>
    );

    console.log(wrapper.dive().debug())

    expect(wrapper.find(DemoPageForm).length).toEqual(1);
  });
});
   console.log src/components/containers/DemoPage.spec.js:23
      <DemoPage store={{...}} />

  ● <DemoPage /> › should contain <DemoPageForm />

    expect(received).toEqual(expected) // deep equality

    Expected: 1
    Received: 0

      23 |     console.log(wrapper.dive().debug())
      24 | 
    > 25 |     expect(wrapper.find(DemoPageForm).length).toEqual(1);
         |                                               ^
      26 |   });
      27 | });

If anyone knows how this can be solved it would be extremely helpful. Cheers

Upvotes: 3

Views: 1518

Answers (2)

LucyMarieJ
LucyMarieJ

Reputation: 132

I've had problems working with shallow rendering with enzyme. Currently, enzyme does not support react hooks fully in shallow rendered test components. You will need to use mount for the moment. You can track progress on the issue page tied to their github in issue #1938

If you want to try using shallow, you do have access to quite a bit of the functionality currently. Try upgrading enzyme-adapter-react-16 to version 1.15.1 or newer to eliminate some of the more glaring issues. It definitely still has issues as of 11/12/19 when I last tried to add it in, but it's getting better all the time as they work on the compatibility issues.

Upvotes: 2

hackape
hackape

Reputation: 19977

Your test fails precisely because shallow just renders one level of the DOM tree. Thus you only get the <Provider /> part of the tree, never reaches <DemoPage />. That also explains why mount works fine.

You probably should read the doc™️ more carefully before you google frenzy mode. Note that shallow(node, options) function accepts a second argument. And there it provides two ways to interact with React Context:

  1. you either pass the Context object to options.context directly
  2. or you can pass the Provider Component carrying that Context to options.wrappingComponent.

Example code to options.wrappingComponent usage:

import { Provider } from 'react-redux';
import { Router } from 'react-router';
import store from './my/app/store';
import mockStore from './my/app/mockStore';

function MyProvider(props) {
  const { children, customStore } = props;

  return (
    <Provider store={customStore || store}>
      <Router>
        {children}
      </Router>
    </Provider>
  );
}
MyProvider.propTypes = {
  children: PropTypes.node,
  customStore: PropTypes.shape({}),
};
MyProvider.defaultProps = {
  children: null,
  customStore: null,
};

const wrapper = shallow(<MyComponent />, {
  wrappingComponent: MyProvider,
});
const provider = wrapper.getWrappingComponent();
provider.setProps({ customStore: mockStore });

Disclaimer: above code is shamelessly copy-pasted directly from the enzyme doc link here.

Upvotes: 0

Related Questions