Redux: Testing connected components that have nested connected components within

Created on 10 Nov 2016  ·  21Comments  ·  Source: reduxjs/redux

Hi, this is more of a question than an issue.

I've recently started using React and Redux for a project and I'm having a couple of issues with testing but the documentation doesn't appear to give a conclusive solution to the problem.

At present I have a connected component that I would like to write some unit tests for, however there is a connected component nested within the first component and I seem to end up being blocked by the props or state (or in most cases both) of the nested component not being set.

I was wondering if there is a way of stubbing one component so that I can focus only on the component I am trying to test?

Thanks in advance

question

Most helpful comment

The concern being raised here is when the component being tested then renders other connected components, either as direct children or somewhere down the hierarchy. If you do a full render via mount(), then the descendant connected components will absolutely try to access this.context.store, and not find it. So, the main options are either make sure the descendants aren't rendered by doing a shallow test, or actually making the store available in context.

All 21 comments

Two things:

1) this is usage and should probably go to stack overflow.

2) part of the convenience of using the connect higher order component
is that you can just write a unit test for the unconnected component and
trust redux to handle piping the store around as it should

If it is unclear what i mean by that i can post an example.

A couple thoughts:

  • One way to focus on just the one component is to use "shallow" rendering, which doesn't actually render any children
  • You can also render <Provider store={testStore}><ConnectedComponent /></Provider> in your tests

FYI, I have links to a number of articles on React/Redux testing over here, which you may find helpful: https://github.com/markerikson/react-redux-links/blob/master/react-redux-testing.md .

And yes, as a usage question, this is really better asked elsewhere.

Thanks @markerikson I'm currently trying the latter method you mentioned but will also bookmark your list of links :)

I still think if you want to test that once component in isolation, then you are better off doing something like

// Component.js
import React from 'react'

export const Component = ({ a, b, c }) => ( 
  // ...
)

// ...

export default connect(mapStateToProps, mapDispatchToProps)(Component)

then you can just bring in the component before its connected in your test like

// Component.spec.js
import { Component } from './Component.js'
import { mount } from 'enzyme'

it('should mount without exploding', () => {
  mount(<Component a={1} b={2} c={3} />)
})

If what you really want to test is that changes to your store are propagating correctly than it makes sense to render the Provider and that is different, though I am not sure if you get much value from testing that.

The concern being raised here is when the component being tested then renders other connected components, either as direct children or somewhere down the hierarchy. If you do a full render via mount(), then the descendant connected components will absolutely try to access this.context.store, and not find it. So, the main options are either make sure the descendants aren't rendered by doing a shallow test, or actually making the store available in context.

@markerikson Ah ok, i just re-read I thought the OP was trying to test the inner component rather than the outer. 👍

Thanks for your help so far guys,

The main issue I'm having at the moment is that I can't seem to pass the state to the connected component that is nested within, thus the view is not being rendered correctly.

Mocking state in general is something I have been struggling with quite a bit actually and have not been able to find anything conclusive on the subject, so it would be great if someone could give me some idea of how I would pass a mocked state to a nested connected or higher order component?

TIA

@StlthyLee : what do you mean by "pass the state"? If you render <Provider store={store}><ComponentWithConnectedDescendants /></Provider> in your test, that should handle things.

Thats the problem, just doing that is not handling it correctly, the component nested within requires a specific parameter from the application state which at present it is not getting for one reason or another.

@StlthyLee probably should put an example/gist/codepen/repo-link or something up, it will take less time to spot the issue since there seem to be terminology clashes here.

@Koleok hopefully this will help clarify

https://gist.github.com/StlthyLee/02e116d945867a77c382fa0105af1bf3

if not please ask, as this is something I am keen to get to the bottom of, having spent the last 9-10 years working in the Rails world and having a good understanding of TDD from that perspective, I'm now trying to get my head around TDD in the React/Redux world.

So I believe I've got to the bottom of this issue now, it wasn't so much a testing issue but more a copy and past error made by another team member, I guess this is part and parcel of having to do write the tests to fit the code rather than my preferred TDD ways. Thanks for all the information given in here it has been extremely valuabe

Just to offer another option that could work for some use cases, I recently ran into this problem with a nested connected component. Rather than make a mock store, I exported the non-connected versions of each component to unit test them without a store. Something like this:

class TopLevelImpl extends React.PureComponent {
  // Implementation goes here
}

// Export here for unit testing.  The unit test imports privates and uses TopLevel
// with Enzyme's mount().
export const privates = {
  TopLevel: TopLevelImpl,
}

export const TopLevel = connect(mapStateToProps)(TopLevelImpl)

I did the same thing for the child nested components so each is testable independently.

Then I took the top-level connected component and passed in the child connected components as props in the app's render() function. When unit testing the top-level component though, instead of passing in the connected components, I just pass in mock components to ensure that they are rendered in the right place. You could also pass in the non-connected components though.

Something like this:

// Root render function
render() {
  return (
    <Provider store={store}>
      <TopLevel child1={<Child1 />} child2={<Child2 />} />
    </Provider>
  )
}

I actually like your idea. But I do something like this instead

const props = {
  child1: () => (<div />),
  child2: () => (<div />)
}
<TopLevel {...props} />

I think that if you already tested child1 and child2 then it is probably no need to have them in your TopLevel component.

I pass a function too if I need to conditionally render the component.

I don't follow your last sentence though. What does testing have to do with passing child components into another component?

Well. For example I have External connected component which uses one or few Internal connected components. In this case if I want to test External component I do smth like this

wrapper = mount(<ExternalComponent.WrappedComponent {...props} />)

However, I have en error that my internal components need state to run properly.

Invariant Violation: Could not find "store" in either the context or props

Then I tried to pass mock state etc. It worked. However in my case I have to do a lot of initialization for every connected component, including async calls, initialState etc and I had other errors related to that.

I think that this will be an overkill to do all proper initialization for all internal connected components If I just want to test External component.

So I've decided to move all connected components out and pass them as props to External connected component. And now I am able to replace them with an empty div's when I want to test.

Oh yeah, that was just the original motivation for the pattern. So to modify your sentence, you meant "I think that if you have already tested child1 and child2, then there is no need to pass them into your TopLevel component when unit testing it."

Agreed.

@StlthyLee, Unfortunately I faced the same problem when trying to test a component that has nested connected components.

A solution I found particularly useful is to connect the nested component to redux store depending on the test environment.

  • When tests run, setup an environment variable NODE_ENV="test". You can catch this variable and make the component receive default properties. In such case the component is not connected to store, and when testing the parent component no issues appear.
  • In other cases make the component connected. This is the default scenario when running the component in the application.

a) First, you'll need a helper function that determines whether the environment is test:

export default function ifTestEnv(forTestEnv, forNonTestEnv) {
  const isTestEnv = process && process.env && process.env.NODE_ENV === 'test';
  const hoc = isTestEnv ? forTestEnv : forNonTestEnv;
  return function(component) {
    return hoc(component);
  };
}

b) Next, defaultProps() and connect() should be applied conditionally depending on the environment:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import defaultProps from 'recompose/defaultProps';

import ifTestEnv from 'helpers/if_test_env';
import { fetch } from './actions';

class MyComponent extends Component {
   render() {
      const { propA, propB } = this.props;
      return <div>{propA} {probB}</div>
   }

   componentDidMount() {
      this.props.fetch();
   }
}

function mapStateToProps(state) {
  return {
    propA: state.valueA,
    propB: state.valueB
  };
}

export default ifTestEnv(
  defaultProps({
    propA: 'defaultA',
    propB: 'defaultB',
    fetch() {   }
  }),
  connect(mapStateToProps, { fetch })
)(MyComponent);

c) Make sure that NODE_ENV is "test" when tests run:

  • When using mocha CLI, put a prefix to set the test environment: NODE_ENV='test' mocha app/**/*.test.jsx.
  • In case of webpack, apply a plugin:
plugins: [
    // plugins...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('test')
      },
    })
]

@markerikson Thanks for the two suggestions. I tried the second one and wrapped my component to test with a Provider. In this case mapStateToProps works correctly but I don't get expected results with mapDispatchToProps. Here the component being tested renders other connected components.

My mapDispatchToProps looks something like

/* @flow */
import { bindActionCreators } from 'redux';

import type { Dispatch } from './types';
import * as actions from './actions';

export default (dispatch: Dispatch, ownProps: Object): Object => ({
  actions: bindActionCreators(actions, dispatch),
});

here the value of actions returned is undefined.

store.js

import { applyMiddleware, compose, createStore } from 'redux';
import { autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';

import rootReducer from './reducers';
import { REHYDRATE } from 'redux-persist/constants';

const store = compose(autoRehydrate(), applyMiddleware(thunk, createActionBuffer(REHYDRATE)))(createStore)(rootReducer);

export default store;

I realise this is quite an old thread but I found this to be a bit of a nuisance issue and thought this solution may help someone who stumbles upon this thread (like me).

As I wasn't actually testing anything related to Redux, a simple solution I found was to mock the connect function to return just the unaltered component that was passed to it.

E.g. in your application you may have a component:


export class ExampleApp extends Component {
  render() {
  }
}

export default connect(
  null,
  { someAction }
)(ExampleApp);

then at the beginning of your test file you can place a mocked connect function to stop Redux interacting with your component:

jest.mock("react-redux", () => {
  return {
    connect: (mapStateToProps, mapDispatchToProps) => (
      ReactComponent
    ) => ReactComponent
  };
});

This mocking will need to placed in every test file that mounts a connected child.

@scottbanyard how do you pass props to the child component ?

Was this page helpful?
0 / 5 - 0 ratings