enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

Issue with shallow testing component handling redux hooks #2282

Open CYB3RL1F3 opened 5 years ago

CYB3RL1F3 commented 5 years ago

Hello,

I have an issue concerning shallowing component with redux provider.

I'm using a useSelector hook from "react-redux", but this one seems not being able to reach the redux context despite this one to be mounted in the wrappingComponent option of shallow.

Here's my code:

import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import { shallow, mount } from 'enzyme';

const mockStore = configureStore([]);
store = mockStore({
   data
});

const wrapper = shallow(<Component />, {
            wrappingComponent: Provider,
            wrappingComponentProps: { store }
        })

I also tried this solution, not working either:

const wrapper = shallow(<Component />, {
            wrappingComponent: ({ children }) => (
                <Provider store={store} children={children} />
            )
        })

And I'm getting the following error:

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

I also tried to dive() the shallowed provider. Same issue.

I think the problem is in the library, cause the lone solution found (and working) is mounting... But I try to avoid mounting for performance reasons. I'm asking here because I don't know if I have to ask enzyme or react-redux but in my opinion, since it's the test issue, asking here seems being pertinent, maybe I'm wrong...

I'm using enzyme-adapter-react-16 as well.

Would you have a solution or will you publish a fix for this?

Thanks :)

neilbryson commented 5 years ago

Seems related to #2202

flavin commented 5 years ago

what I did was something like this:

const wrapper = shallow(
    <Provider store={store}>
         <Component />
     </Provider>
).dive(/* Provider */).dive(/* <Connect> */).dive(/*Consumer*/).dive(/*Component*/);

I don't like too much how verbose was but at least worked

Mathijs003 commented 4 years ago

That's not working for me

hb0 commented 4 years ago

what I did was something like this:

const wrapper = shallow(
    <Provider store={store}>
         <Component />
     </Provider>
).dive(/* Provider */).dive(/* <Connect> */).dive(/*Consumer*/).dive(/*Component*/);

I don't like too much how verbose was but at least worked

Does not seem to work, or did I get something wrong?

Screenshot from 2020-05-06 21-06-49

JuliusKoronci commented 4 years ago

what I did was something like this:

const wrapper = shallow(
    <Provider store={store}>
         <Component />
     </Provider>
).dive(/* Provider */).dive(/* <Connect> */).dive(/*Consumer*/).dive(/*Component*/);

I don't like too much how verbose was but at least worked

It doesnt work with the new hooks

Mathijs003 commented 4 years ago

I think I fixed this using mount instead of shallow, and giving in a mockstore like this:

describe('NotificationsTable', () => {
  let store: any;
  const middlewares: any[] = [];
  const mockStore = configureStore(middlewares);

  beforeEach(() => {
    store = mockStore({});
  });

  it('should render correctly', () => {
    const component = mount(
      <Provider store={store}>
        <NotificationsTable />
      </Provider>
    );

    expect(component.find(NotificationsTable)).toHaveLength(1);
    expect(component.find(Table)).toHaveLength(1);
  });
});

As you can see I'm able to find things in the component without any problems

RShergold commented 4 years ago

Is there any roadmap for when Enzyme will support Redux useSelector?

My team work on quite a large Redux app and the lack of support in Enzyme means we can't currently use this feature. We need to decide pretty soon if we begin transitioning away from Enzyme to something like React Testing Library but obviously I'd prefer to stick to one way of doing things.

Any sense of a timeframe or even if this is simply a wontfix would be greatly appreciated.

Lelith commented 4 years ago

Is there an official recommendation to just use mount or is there any other update on the topic?

adelchamas96 commented 4 years ago

there might be a solution is to mock the redux hooks. here full example

aashayamballi commented 4 years ago

You can mock react-redux hooks using this.

If your react project has been created using create react app then you have to create a folder named __mocks__ inside ./src directory. Now inside the __mocks__ folder create a file named react-redux.js

Inside react-redux.js we need to add the below lines

const mockDispatch = jest.fn()
const mockSelector = jest.fn()

const mockState = (state = {}) => {
    return mockSelector .mockImplementationOnce(callback => {
        return callback(state);
    })
}

module.exports = {
    ...jest.requireActual('react-redux'),
    __esModule: true,
    useSelector: mockSelector ,
    useDispatch: () => mockDispatch,
    mockDispatch,
    mockState
};

Jest docs says to keep the __mocks__ folder at the same level where node_module is located. But if your project is created using create-react-app then we need to keep it inside ./src the folder structure would like ./src/__mocks__/react-redux.js

This is actually an active issue, and here is the GitHub issue link for it node_modules Mocks are not picked up by jest with latest react-scripts

So now in test cases you just need to import mockSelector and mockDispatch as would normally import a module

for ex: import { mockSelector, mockDispatch } from "react-redux";

If your code has multiple useSelector then your test case might look like this

import { shallow} from 'enzyme';
import {mockDispatch, mockSelector} from "react-redux";

test('sample test case', () => {
        mockState({
            reducerName0: {
                someKey: [
                    {
                        ... some data
                    },
                ]
            }
        });

        mockState({
            reducerName1: {
                someKey: [
                    {
                        ... some data
                    },
                ]
            }
        });

        mockState({
            reducerName2: {
                someKey: [
                    {
                        ... some data
                    },
                ]
            }
        });

        const wrapper = shallow(<YourComponent/>);
        expect(mockDispatch).toHaveBeenCalledWith({
            type: "SOME_TYPE",
            payload: "somePaylod"
        });

    });

This is actually working for me with shallow render. Hope it will help you too.

maxizhukov commented 3 years ago

I have same problem:

could not find react-redux context value; please ensure the component is wrapped in a

  28 |
  29 |   const { t } = useTranslation();
> 30 |   const dispatch = useDispatch();
kael-shipman commented 1 year ago

I should say up front, I'm really fumbling around in the dark a little on this one because, while I'm a very experienced back-end Typescript engineer, I'm still somewhat fresh to the front-end.

That said, I did find a simple solution that worked for me. Essentially, you import * as reactRedux from 'react-redux', then simply replace the hook functions with your mocks:

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

describe("MyDeepComponent", () => {
  const originals = {};
  let selectedData;
  let dispatchedActions;

  beforeAll(() => {
    originals.useDispatch = reactRedux.useDispatch;
    originals.useSelector = reactRedux.useSelector;
    reactRedux.useDispatch = () => (action) => dispatchedActions.push(action);
    reactRedux.useSelector = (selector) => selectedData;
  });

  afterAll(() => {
    reactRedux.useDispatch = originals.useDispatch;
    reactRedux.useSelector = originals.useDispatch;
  });

  beforeEach(() => {
    selectedData = {};
    dispatchedActions = [];
  });

  test('should do thing', () => {
    selectedData = {
      thing: {
        id: 1
      }
    };

    const wrapper = shallow(<MyDeepComponent />);
    expect(wrapper.find(SomeChild)).toHaveLength(1);
    expect(dispatchedActions.find(a => a.type === 'MY_ACTION_TYPE')).not.toBeUndefined();
  });

  // ...
});