Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing a Redux-connected component using Enzyme #1002

Closed
mrsaeeddev opened this issue Jun 23, 2017 · 80 comments
Closed

Testing a Redux-connected component using Enzyme #1002

mrsaeeddev opened this issue Jun 23, 2017 · 80 comments
Labels

Comments

@mrsaeeddev
Copy link

mrsaeeddev commented Jun 23, 2017

Enzyme

i) What is the least error-prone way to test a Redux-connected Componet using Enzyme?
I have checked many links and articles but haven't found a satisfactory answer. It's confusing me a lot.
ii)How can I test whether my component is getting certain props or not using Enzyme?

Versions

  • React-Boilerplate : Current Version
  • Node/NPM: ^6
  • Browser: Chrome
  • Enzyme: Current Version
@ljharb
Copy link
Member

ljharb commented Jun 23, 2017

const wrapper = shallow(<ConnectedComponent />).dive();

and then make your assertions on the wrapped component.

@gecko25
Copy link

gecko25 commented Jul 26, 2017

Any update to this? I secondly this very strongly. Everything I have tried does not work.

I set the context like shown in the docs -- http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html

This also looked promising
https://stackoverflow.com/questions/37798741/nested-components-testing-with-enzyme-inside-of-react-redux

But usually everything leads me back to:

Invariant Violation: Could not find "store" in either the context or props of "Connect(DatePicker)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(DatePicker)".

😢

@ljharb
Copy link
Member

ljharb commented Jul 26, 2017

There's no update needed; wrap your connected component and use .dive().

@gecko25
Copy link

gecko25 commented Jul 26, 2017

Two things:

  1. This is not working for me.

const wrapper = shallow(<ConnectedDatePicker />).dive();

I still get the same ole error:

Invariant Violation: Could not find "store" in either the context or props of "Connect(DatePicker)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(DatePicker)".

  1. I need to use mount anyways. If I try something like:

const wrapper = shallow(<ConnectedDatePicker store={store}/>).dive();

I get

Method “props” is only meant to be run on a single node. 0 found instead.

when my test tries to use the API method find. (wrapper.find('#startDate').simulate('focus');)

I am assuming it cannot find it, because it is trying to find an grandchild element of my DatePicker component.

@ljharb
Copy link
Member

ljharb commented Jul 26, 2017

I'd generally recommend using only shallow as much as possible, and only making assertions on which components your thing renders - in other words, if A renders B which renders C, your A tests shouldn't know anything about C, and should only be asserting that A renders B with the right props. Your B tests should be asserting things about C.

@gecko25
Copy link

gecko25 commented Jul 26, 2017

Right. Makes sense. What I am trying to do is more of an integration test.

I am sort of new to making tests. Would this thinking be more correct:

If my DatePicker component has child components that send events up to my DatePicker Component (for example, when a user selects July 21, 2017 a <div/> is clicked & the event bubbles up and eventually is handled by my DatePicker component's onDateChange function).

For my test, rather than simulating that click event. I should be directly testing my onDateChange function with mock data under various circumstances.

(I apologize if this got off scope a little bit, just trying to understand).

@ljharb
Copy link
Member

ljharb commented Jul 26, 2017

Yes, you definitely should be unit-testing your onDateChange.

It's your date picker component's tests' job to ensure that onDateChange is called at the proper time.

@gecko25
Copy link

gecko25 commented Jul 26, 2017

thank you, this has been very helpful

@ljharb ljharb closed this as completed Jul 26, 2017
@shakiba
Copy link

shakiba commented Sep 3, 2017

@ljharb Is there any way to mock the inner component? Instead of shallow/dive.

@ljharb
Copy link
Member

ljharb commented Sep 3, 2017

@shakiba Mocks make tests more brittle; but shallow rendering is kind of like automocking. Can you elaborate on what you're trying to achieve?

@shakiba
Copy link

shakiba commented Sep 4, 2017

@ljharb The reason that shallow does not work for me is that there are other nested components which I want to be rendered, such as bootstrap-react layout components. Basically instead of including specific components in rendering using dive, I want to exclude few components from being rendered. I solved it using jest.mock, but if you have any other idea please let me.

@ljharb
Copy link
Member

ljharb commented Sep 4, 2017

enzyme v3 and React 16 will provide an API for that; short of that, that's probably the best option.

@przemuh
Copy link

przemuh commented Sep 21, 2017

Maybe it will be helpful for someone:

If you want to find if connected component is rendered you can find it by Connect(Component) selector.

Example (StylePicker is connected with the store):

index.js

render() {
        return (
            <div>
                <Header>Choose style</Header>
                <StylePicker {...{ styles }}/>
                <NavigationButtons
                    onPrev={this.goToHome}
                    onNext={this.goToLayoutPicker}
                />
            </div>
        );
    }

index.test.js

    it('should render <StylePicker>', () => {
        expect(shallow(<MainComponent />).find('Connect(StylePicker)')).toHaveLength(1);
    });

@ljharb
Copy link
Member

ljharb commented Sep 22, 2017

@przemuh better would be shallow(<MainComponent />).dive().

@mstorus
Copy link

mstorus commented Sep 27, 2017

@ljharb regarding the question in #1002 (comment), don't we still need to use something like redux-mock-store in order for shallow(<MainComponent />) to work? whether or not we use .dive() won't affect that shallow(<MainComponent />) expects to have a store in either context or props, right?

@ljharb
Copy link
Member

ljharb commented Sep 27, 2017

@mstorus correct, it won't affect that.

@dschinkel
Copy link

dschinkel commented Sep 29, 2017

sometimes you might have to double dive (someContainer.dive().dive() or whatever before you can start working on the component) depending on how your connected container is setup.

For example lets say you're wrapping your connected container in another wrapper like this, you'll possibly have to double dive I've found because you need to now get past the ReactTimeout HOC and dive to get past the react-redux connect wrapper HOC in order to work with the container.

const TimeoutLive = ReactTimeout(connect(mapStateToProps, {
  fetchLive,
  fetchLivePanel,
  fetchLiveVideo,
  clearLive,
  saveLiveRestart,
})(Live));

@ljharb
Copy link
Member

ljharb commented Sep 29, 2017

Totally correct - you need one .dive() per HOC.

@michaelBenin
Copy link

Is there any syntactic sugar available for components wrapped with react-router's withRouter method?

Instead of a test like:

shallow(
        <StaticRouter context={{}}>
          <IndexPage />
        </StaticRouter>
      ).dive().length

@ljharb
Copy link
Member

ljharb commented Oct 4, 2017

@michaelBenin .dive() is the sugar.

@trevorwhealy
Copy link

trevorwhealy commented Oct 27, 2017

If you don't need access to the redux store as part of your unit test, don't forget that you can also export the React component individually in addition to exporting the connected variant by default.

export class Component extends React.Component {}

export default connect(({ someReducer }) => ({
  ...
}))(Component)

In your test, just extract the "pure" Component rather than the connected one:

import { mount } from 'enzyme'  
import { Component } from '../components/Component'
...
mount(<Component />)

@ljharb
Copy link
Member

ljharb commented Oct 27, 2017

You shouldn't do that, however, because if it's never used by itself in production, there's no value in testing it by itself. Use .dive().

@trevorwhealy
Copy link

trevorwhealy commented Oct 27, 2017

I wouldn't say that it has no value @ljharb but I also know what you mean. Using shallow with dive was not working for my use case. I got the error: Method “props” is only meant to be run on a single node. 0 found instead

renderComponent = (props = {}) => shallow(<Component {...props} />).dive()

const dispatchMock = jest.fn()
const component = renderComponent({ dispatch: dispatchMock })

component.find('form').simulate('submit')
expect(dispatchMock.mock.calls.length).toBe(1)  

@ljharb
Copy link
Member

ljharb commented Oct 27, 2017

@trevorwhealy that's worth exploring - i don't see any .props() calls in that code, nor is dispatchMock used anywhere - but it doesn't change that exporting the "pure" component just for testing isn't a good practice.

@trevorwhealy
Copy link

@ljharb sure we can agree on that. updated the code to reflect the implementation more closely.

@grahaml
Copy link

grahaml commented Nov 28, 2017

Referencing @gecko25's comment above - I get that same error...

shallow(<Provider store={store}><MyContainer/></Provider>).dive()

...

Invariant Violation: Could not find "store" in either the context or props of "Connect(MyContainer)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(MyContainer)"

The connected component has some internal state that I want to test.

@WeishenMead
Copy link

What happens if there are multiple children of the component, one of which is connected?

<Wrapper>
    <Component/>
    <ConnectedComponent/>
</Wrapper>

In that case .dive() won't work because I have multiple non-dom children.

I can get around this by doing

shallow(<Wrapper/>).children().at(1).dive()

but that makes for a really fragile test. I suppose in that situation I can do what was suggested above:

shallow(<Wrapper/>).find('Connect(ConnectedComponent)').dive()

but I'm curious if there's a better solution.

@WeishenMead
Copy link

Redux's connect creates a new higher-order component wrapping the component you want. So dive accessed the one child of the Redux wrapper, which is the actual component you're looking for.

@viksok
Copy link

viksok commented Oct 18, 2018

@przemuh better would be shallow(<MainComponent />).dive().

had a similar issue.
using dive() does not resolve it, but explicit store={mockStore({})} prop does.
shallow(<MainComponent store={mockStore({})}/>)

with dive() I get such an error:
Invariant Violation: Could not find "store" in either the context or props of "Connect(UserGuideButton)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(UserGuideButton)".

@ljharb
Copy link
Member

ljharb commented Oct 19, 2018

you may need to pass the context manually when using dive, pending a known issue

@BalasubramaniM
Copy link

Finally, I somehow made it work which may help someone. Just pass your store to your component like said above.

import Home from "../components/Home";
import { store } from "../store";

describe("Home", () => {
	it("Should render component", () => {
		const wrapper = shallow(<Home store={store} />);
		expect(wrapper.length).toEqual(1);
	});
});

But after doing it so, I met an another error called,

Invariant Violation: Browser history needs a DOM

After searching for other answers in SO, I found that, I was using createBrowserHistory throughout out my application, in store, whereas createMemoryHistory is more than enough to test unit test cases.

All the test cases have been passed, after changing it so.

Note: I am just new to Jest and correct me if it go wrong at any specific cases.

@hutber
Copy link

hutber commented Nov 7, 2018

store

Where did you get the store from?

@ncpope
Copy link

ncpope commented Nov 8, 2018

I too struggled with this one for a bit piecing together what I found on various related results. This is my working test: https://gist.github.com/ncpope/a9da1698d919108f104cd1d7e4bec5e9

I am additionally using Redux Mock Store to achieve these results.

@shorif2000
Copy link

how do i use shallow with mount. i am trying to do this

@karolisgrinkevicius-home24

I have written the helper which I use once I need the redux connected components to test. Here it is:

import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import configureMockStore from 'redux-mock-store';

const defaultStore = { responsive: { fakeWidth: 1200 } };
const mockedStore = configureMockStore()(defaultStore);

export const mountWithProvider = children => (store = mockedStore) =>
  mount(<Provider store={store}>{children}</Provider>);

Then you're able to use it as follows:

const props = {};
const wrapper = mountWithProvider(<SomeConnectedComponent {...props} />)();
expect(wrapper.exists()).toBe(true);

Also you can set custom state of the store to override the default one defined in the helper scope:

const myCustomState = {
  myName: 'test',
  myAge: 9999
};
const wrapper = mountWithProvider(<SomeConnectedComponent {...props} />)(myCustomState);

@RrNn
Copy link

RrNn commented May 8, 2019

@ljharb this works for me const wrapper = shallow(<ConnectedComponent />).dive();, but I'm curious to know why? My scenario was, a component that has another component as a child, both of which are connected. So the code looks something like

class Session extends React.Component {
...
render() {
<div>
...
<MealSessionModal propOne={propOne} />
...
<div>
}
}

MealSessionModal is connected too, remember.
So in my tests, I did

const wrapper = mount(
    <BrowserRouter>
      <Sessions store={store} {...props} />
    </BrowserRouter>
  )

but this was throwing th error
Invariant Violation: Could not find "store" in either the context or props of "Connect(MealSessionModal)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(MealSessionModal)".
which was pointing to the child MealSessionModal. so I ended up using

const wrapper = shallow(<Sessions store={store} {...props} />).dive()

which worked fine. My other question is, how could I use the mounted parent Sessions in my tests without using shallow and then diving? And why does shallowing and diving work?

sunkibaek added a commit to sunkibaek/react-native-paginatable that referenced this issue Jun 4, 2019
@mmaya
Copy link

mmaya commented Jun 6, 2019

const myCustomState = {
  myName: 'test',
  myAge: 9999
};
const wrapper = mountWithProvider(<SomeConnectedComponent {...props} />)(myCustomState);

Thanks so much! Your solution solved most of my problems :D

@951565664
Copy link

Two things:

  1. This is not working for me.

const wrapper = shallow(<ConnectedDatePicker />).dive();

I still get the same ole error:

Invariant Violation: Could not find "store" in either the context or props of "Connect(DatePicker)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(DatePicker)".

  1. I need to use mount anyways. If I try something like:

const wrapper = shallow(<ConnectedDatePicker store={store}/>).dive();

I get

Method “props” is only meant to be run on a single node. 0 found instead.

when my test tries to use the API method find. (wrapper.find('#startDate').simulate('focus');)

I am assuming it cannot find it, because it is trying to find an grandchild element of my DatePicker component.

Has the first question been solved? I only saw you discussing mount and shallow,And I still report an error now.

@olyayakimovich
Copy link

I had the same problem. This solution worked for me:
const wrapper = mount(<Provider store={store}><ConnectedComponent /></Provider>, { context: { store }, childContextTypes: { store: PropTypes.objectOf(PropTypes.any), }, });

@chadlof
Copy link

chadlof commented Oct 9, 2019

I'd generally recommend using only shallow as much as possible, and only making assertions on which components your thing renders - in other words, if A renders B which renders C, your A tests shouldn't know anything about C, and should only be asserting that A renders B with the right props. Your B tests should be asserting things about C.

The problem I have ai A is wrapped with withRouter and connect HOC's
In order to test A I need to go two layers down

@adrianbw
Copy link

const wrapper = mountWithProvider(<SomeConnectedComponent {...props} />)(myCustomState);

This was super helpful. Thank you.

@chulijimmi
Copy link

I have written the helper which I use once I need the redux connected components to test. Here it is:

import { Provider } from 'react-redux';
import { mount } from 'enzyme';
import configureMockStore from 'redux-mock-store';

const defaultStore = { responsive: { fakeWidth: 1200 } };
const mockedStore = configureMockStore()(defaultStore);

export const mountWithProvider = children => (store = mockedStore) =>
  mount(<Provider store={store}>{children}</Provider>);

Then you're able to use it as follows:

const props = {};
const wrapper = mountWithProvider(<SomeConnectedComponent {...props} />)();
expect(wrapper.exists()).toBe(true);

Also you can set custom state of the store to override the default one defined in the helper scope:

const myCustomState = {
  myName: 'test',
  myAge: 9999
};
const wrapper = mountWithProvider(<SomeConnectedComponent {...props} />)(myCustomState);

Cool bro, now I can see the child props

@Unicornelia
Copy link

Providing an example of using redux-mock-store, since this thread still comes up a lot in searches.

This example includes redux-thunk, since it's a common middleware used with Redux (and is mentioned in the Advanced section of the Redux tutorial). If you don't use redux-thunk, just remove that part.

import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';

const store = configureStore([
    thunk,
])();

const wrapper = shallow(
  <MyComponent {...props} store={store} />
).dive();

Tested with "redux-thunk": "^2.1.0" and "redux-mock-store": "^1.5.1".

Note that using .dive() ensures that you are testing the connected component (which is actually used in production), rather than the unconnected component (which may not be directly used in production).

Omg, thank you! After hours of trying, this finally worked. <3

@KarinGarciaZ
Copy link

@trevorwhealy This worked, but what if I have inner components we I only export the connect(component)? This only would help with the route component, so how do I handle that?
Thanks.

If you don't need access to the redux store as part of your unit test, don't forget that you can also export the React component individually in addition to exporting the connected variant by default.

export class Component extends React.Component {}

export default connect(({ someReducer }) => ({
  ...
}))(Component)

In your test, just extract the "pure" Component rather than the connected one:

import { mount } from 'enzyme'  
import { Component } from '../components/Component'
...
mount(<Component />)

@ljharb
Copy link
Member

ljharb commented Aug 6, 2020

@KarinGarciaZ .dive().

@carolozar
Copy link

carolozar commented Sep 5, 2020

I am getting the same issue, I am using IntlProvider

this is how I mount the component

function nodeWithIntlProp(node) {
  return React.cloneElement(node, { intl });
}

export function mountWithIntl(node) {
  return mount(
    <IntlProvider locale={en.locale} messages={en.messages}>
      {nodeWithIntlProp(node)}
    </IntlProvider>,
  ).dive();
}

the .dive() doesnt work with this, now I get this error
Invariant Violation: [React Intl] Could not find required `intl` object. <IntlProvider> needs to exist in the component ancestry.

@ljharb
Copy link
Member

ljharb commented Sep 5, 2020

@carolozar use the wrappingComponent option to pass the IntlProvider. You shouldn't need nodeWithIntlProp at all.

@chase2981
Copy link

What if I don't want to test the redux because we are undergoing a process of normalizing our store and testing the connected components with a mock store makes our tests extremely brittle. We change the store and now we got tens if not hundreds of tests failing.

To be clear, I'm just looking for a non hacky standard way of testing a non-connected component that has connected component children.

@ljharb
Copy link
Member

ljharb commented Oct 9, 2020

@chase2981 if you use the component in your real app without the connection wrapper, then test it that way - if not, then it’s correct that hundreds of tests start failing when you change the store in a breaking way.

That friction is a useful indicator that perhaps you should design APIs so they can evolve in non-breaking ways :-)

@chase2981
Copy link

chase2981 commented Oct 9, 2020 via email

@chase2981
Copy link

This guy recommends not even testing your container components and just structuring your app a little differently and testing everything else individually. https://link.medium.com/1Cd79oA3qab

@ljharb
Copy link
Member

ljharb commented Oct 9, 2020

The exact functionality you should be trying to test is "how your component works" - the store connection is absolutely part of that.

Proper unit testing does NOT just test "the code under test", it fully integration-tests the thing it's testing.

@QasimRRizvi
Copy link

Below solutions are working for me.

const wrapper = shallow(
  <ConnectedComponent />,
  { context: { store } }
).dive();

const wrapper = shallow(
  <Provider store={store}>
    <ConnectedComponent />
  </Provider>
).dive({ context: { store } }).dive();

const wrapper = shallow(
  <ConnectedComponent store={store} />,
).dive();

I've tried all of these but same error

 wrapper = shallow(
      <Provider store={store}><Login /></Provider>
    ).dive({ context: { store } }).dive();
Could not find "store" in the context of "Connect(LoginBase)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(LoginBase) in connect options.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests