Enzyme: shallow rendering tries to render child components -- need stubs

Created on 12 Feb 2016  路  32Comments  路  Source: enzymejs/enzyme

We have nested connected redux components. We would like to be able to atomically test each component without having to wrap our test components in a redux Provider with store etc.

It would be great if enzyme could stub or mock child components.

Need More Information

Most helpful comment

@softage0 thanks for the link! Here it is again with the anchor for the lazy: #connected-components

I'm still a little lost though. I understand how to test an individual component that is connected — in fact, in my example repo I split the pure component and connected component into separate files. So testing the pure component itself in isolation is no problem, I just import the default export from the file with the pure component.

The issue I have is when I want to test (shallow render) a component that has a connected component as a child. I don't think it makes sense to rewrite the entire parent component in the test environment with separate import statements for the connected child, and conditionally importing the pure child component or the connected child component depending on whether the parent component is being rendered in a test environment or elsewhere seems hackish and wrong, too.

Perhaps the solution is simply that having a connected component as a child of an otherwise pure/stateless component that you want to shallow render and test in isolation is an architectural anti-pattern.

All 32 comments

@timothyjlaurent the shallow renderer will never instantiate or call render on components other than the one passed to shallow directly. Are you sure this is happening?

Can you put up some example code to demonstrate the problem? Thanks.

I'm sure of it. Originally this wasn't a big problem as all that I had to contend with were proptype warnings but now with rendering a connect decorated redux component it is freaking out.

here is the spec:

import React from 'react'
import ReactDOM from 'react-dom'
import ReactTestUtils from 'react-addons-test-utils'
import Immutable from 'immutable'
import { shallow, mount } from 'enzyme'

import { GeneCentricView } from '../../../path/to/geneCentricView'

  describe('base component', function() {
    beforeEach(function () {
      this.props = {
         data :  'val'
        })
      }
    });

    it('renders as a div', function () {
      const wrapper = shallow(<GeneCentricView  {...this.props} />)
      expect(wrapper.is('div')).toBe(true)
    });

here is the component I am shallow rendering:

...
import GeneDetailsSection from './geneDetailsSection';
import MolecularConditionInformationSection from './molecularConditionInformationSection';
import InternalNotesSection from './InternalNotesSection';


export class GeneCentricView extends React.Component {
  static propTypes = {
    blah...
  };

  render() {
    return (
    <div>
      <div className="gene-heading row" >
        <div className="text-center">
          <span className="cat-icons" />
          <span className="gene-title" >{this.props.gene.getIn(['object', 'docid'])}</span>
          <FontAwesome name="history" />
        </div>
      </div>
      <div className="network-builder">
        Network Builder
      </div>
      <GeneDetailsSection {...this.props} />
      <MolecularConditionInformationSection {...this.props} />
      <InternalNotesSection {...this.props} />
    </div>
    )
  }
}

and here one of the child components

import connect from 'react-redux'

class GeneDetailsSection extends React.Component {
static proptypes= {
  foo: React.Proptypes...
},   

render(){
    return (
      <div>

            {content here}
      </div>
    )
  }
}

export function mapStateToProps(state, props) {
  const geneId = String(props.geneId)
  return {
    transcript: Immutable.fromJS({
      object: state.collections.getIn(['transcript', 'objects', props.gene.getIn(['object', 'transcripts', 0])]),
      schema: state.collections.getIn(['transcript', 'schema'])
    })
  }
}

export default connect(mapStateToProps)(GeneDetailsSection)

I also tried this with the TestUtils and got the same issue... Doesn't really seem that 'shallow' in either case.

How are you detecting that render of GeneDetailsSection is being called?

Well I was getting the PropTypes warning when not decorated and when trying to use it as a connected Redux component, export default connect(mapStateToProps)(GeneDetailsSection), the connect HOC couldn't find store on context and wouldn't allow the parent component, GeneCentricView, to render and the test then failed.

+1
It also happens to me. looking forward for a fix.

error:

AssertionError: expected <App /> to contain [Function: Header] HTML: Not available due to: Could not find "store" in either the context or props of "Connect(Header)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Header)"

code: (without external files like webpack config, karma config, test loader etc)
test:

import { shallow } from 'enzyme';

import App from 'components/app';
import { Header } from 'components/header';

const wrapper = shallow(<App />);

describe('<App />', () => {
  it('renders header', () => {
    expect(wrapper).to.contain(Header);
  });
});

app:

import Header from 'components/header';

const App = () => (
  <div>
    <Header />
  </div>
);

export default App;

header:

import { connect } from 'react-redux';
import { AppBar, RaisedButton, RefreshIndicator, CircularProgress } from 'material-ui';

import { fetchMigrations, executeMigrations } from 'actions/migrations';

export const Header = ({ ui, fetchMigrations, executeMigrations }) => {
  const getExecuteButtonContent = () => {
    if (!ui.statuses.executeButton || ui.statuses.executeButton === 'ready') {
      return <span>execute</span>;
    } else {
      return (
        <div className='progress'>
          <CircularProgress size={0.4}/>
        </div>
      );
    }
  };

  return (
    <header className='header'>
      <AppBar title='Cassandra Migrations'
              className='app-bar'
              showMenuIconButton={false}
              iconElementRight={
                <div className='header-actions'>
                  <div className='sync-button' onClick={fetchMigrations}>
                    <RefreshIndicator percentage={100}
                                      left={10}
                                      top={0}
                                      status={ui.statuses.syncButton || 'ready'}
                                      loadingColor={'white'}
                                      style={{ backgroundColor: '#36C6D3' }}
                    />
                  </div>
                  <RaisedButton className='execute-button'
                                primary
                                onMouseDown={executeMigrations}>{getExecuteButtonContent()}</RaisedButton>
                </div>
              }/>
    </header>
  );
};

const mapStateToProps = ({ ui }) => ({ ui });

export default connect(mapStateToProps, { fetchMigrations, executeMigrations })(Header);

Alright - I'm also receiving this error and think I can pin down what's happening:

  • I am only getting errors on components that are decorated/HOC components and are exported from their module as default.
  • I do _not_ get errors if I test the component directly. This is because I am _not_ testing the decorated component - I'm only testing the 'undressed' component.
  • Third, the errors popup when I test components that _import_ these decorated components.

It sounds like enzyme tries to render decorated components (probably because the return value of that import is a function and not a component). I'll try and put together a repro, but these are the warnings I get all over my test suite.

PhantomJS 2.1.1 (Linux 0.0.0) ERROR: 'Warning: Failed propType: Required prop `submitting` was not specified in `AssertionForm`.'
PhantomJS 2.1.1 (Linux 0.0.0) ERROR: 'Warning: Failed propType: Required prop `submitting` was not specified in `HookForm`.'
PhantomJS 2.1.1 (Linux 0.0.0) ERROR: 'Warning: Failed propType: Required prop `form` was not specified in `Select`.'
PhantomJS 2.1.1 (Linux 0.0.0) ERROR: 'Warning: Failed propType: Required prop `deleteTest` was not specified in `TestsOverview`.'
PhantomJS 2.1.1 (Linux 0.0.0) ERROR: 'Warning: Failed propType: Required prop `location` was not specified in `TestsOverview`.'

_My setup is basically the same as the above comments (babel w/ es6, karma test runner, and enzyme - nothing special besides that)._

I believe I'm running into this as well (specifically: cannot shallow render components that contain decorated components). I've thrown together a contrived example repo here by modifying the redux counter example.

In the example, I have a <Root /> component that contains a <CounterContainer /> which wraps a <Counter /> component with connect from react-redux.

Is it possible to shallow render this <Root /> with enzyme as I've attempted in test/components/root.spec.jsx? I get an Invariant Violation error related to not passing the store to <Counter /> — but I don't care about <Counter /> in this test case, which is why I'm using shallow. I just want to test <Root /> in isolation.

Apologies if I'm missing something obvious :grin:

Connected Components section of the following link would be helpful:
http://redux.js.org/docs/recipes/WritingTests.html

@softage0 thanks for the link! Here it is again with the anchor for the lazy: #connected-components

I'm still a little lost though. I understand how to test an individual component that is connected — in fact, in my example repo I split the pure component and connected component into separate files. So testing the pure component itself in isolation is no problem, I just import the default export from the file with the pure component.

The issue I have is when I want to test (shallow render) a component that has a connected component as a child. I don't think it makes sense to rewrite the entire parent component in the test environment with separate import statements for the connected child, and conditionally importing the pure child component or the connected child component depending on whether the parent component is being rendered in a test environment or elsewhere seems hackish and wrong, too.

Perhaps the solution is simply that having a connected component as a child of an otherwise pure/stateless component that you want to shallow render and test in isolation is an architectural anti-pattern.

@codekirei described the problem perfectly. Testing connected components isn't the problem, it's testing components that have children that are connected (or are otherwise decorated).

I don't think that it's an anti-pattern to have nested decorated components. Namely, because they're not always decorated with @connect. For example, I have a collapsible HOC, or even something like redux-form. But enzyme tries to shallow render it, when it should just make a stub.

From what I can tell, this issue is happening because redux uses context and expects the global store to be in this.context.store of the HOC.

It's not entirely clear to me that shallow rendering of component A should be affected by the context required by a child component, but potentially it is.

If this is indeed the case, the good news is enzyme allows you to provide context in shallow:

const wrapper = shallow(<Foo />, { context: { store: mockStore() } });

@lelandrichardson Thanks a ton, that's very helpful. However, we're not _always_ talking about connect; there other HOC's that don't use context.

For example, say I have a function (HOC) called collapsible that looks like this:

return function() {
    return function(Component) {
        return class extends React.Component {
            state = {
                collapsed: false
            }
            // other methods...
            render() {
                return (<Component {...this.state}/>)
            }
        }
    }
}

And then I use collapsible in my Thing1 component:

import collapsible from './collapsible'

class Thing1 extends React.Component {
    render() {
        return (<Component {...this.state}/>)
    }
}

export default collapsible(Thing) // Only exporting the decorated version!

So far, so good.

Now, Thing2 imports Thing1:

import Thing1 from './Thing1' // NOTE: I imported the default, decorated, version

export default class Thing2 extends React.Component {
    render() {
        return(
            <div><Thing1/></div>
        )
    }
}

Now we test Thing2 (which has the decorated Thing1 in it's children):

import { shallow } from 'enzyme'
import Thing2 from './Thing2'

const wrapper = shallow(<Thing2/>)
expect(wrapper.type()).toEqual('div') //passes
expect(wrapper.find('Thing1')).toBe(1) // passes

It all passes, but I get these errors:

ERROR: 'Warning: Failed propType: Required prop `collapsed` was not specified in `Thing1`.'

How can I go about _not_ throwing those errors when testing a component that has children using collapsible?

I've added the store to context in the shallow render of the parent component, but I still get the exact same error regarding the child component:

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

I've updated my example repo here.

@lelandrichardson Sorry to bug, but have you had a chance to take a look at reproducing this one?

Also running into this issue. Seems like a bug to me.

@codekirei I think your issue is caused by chai-enzyme. See https://github.com/producthunt/chai-enzyme/issues/13 and https://github.com/codekirei/enzyme-with-react-redux/pull/1. When you run into a problem like this you should try to strip everything out to create a minimal reproduction. For example, just doing npm install on your repro repo took forever for me. I'm guessing because of the Webpack deps, but I'm not sure. In any case it just goes to show that there's way too much going on in your repo to isolate the issue to enzyme.

@CodyReichert Failed propType warnings are not related to rendering the component generating the warnings. They're emitted at React.createElement time. Your collapsible example is really confusing because you don't show propTypes being set anywhere.

@timothyjlaurent It's difficult to understand what scenario you're describing (like what "freaking out" means). If the same thing happens with TestUtils that doesn't really sound like an issue with this library though. Given that, this is probably besides the point, but you're importing both shallow and mount in the same test file -- you're sure the problem you were having was with shallow rendering? Again, trying to debug something like this is basically hopeless without creating a minimal reproduction.

im getting the same issue with a redux container that has container children. anyone found a way around this?

^+1

My issue was I was logging .html() use .debug() and everything's fine

Having the same issue, and it's triggered when using .html(). Wondering what the solution is. I'd like to test for the html the component produces.

.html() is always a full render. How could you have an HTML string for a <Foo />?

I was able to use .shallow() for this issue instead of .html().

I haven't looked deep enough, but it seems that .contain() mounts the component just like .html() does. Chai below:

expect(renderedComponent).to.contain(<ul />);

Is resolved by changing to:

expect(
  renderedComponent.find('ul')
).to.be.present();

@designbyadrian please report that to chai-enzyme.

FYI, there's a red herring around this error message when using chai-enzyme. If expect(wrapper).to.contain(expectedValue); fails, the test output will print this message as part of the diff. The contains legitimately fails, but it looks like the test failed because of the rendering issue.

It seems like chai-enzyme is using wrapper.html() (or an equivalent) to print out the diff, which causes this error.

It should be using .debug() instead - @mindblight, would you mind filing an issue and/or PR on chai-enzyme about that?

For rendering connected components within another component which is tested and giving this kind of error: Invariant Violation: Could not find "store" in either the context or props of ....
In my case helped to switch to mount and adding childContextTypes:

// some_other.js
export default connect(....)(({}) => (
  <div>
   ....
 </div>
))

// app.js
import SomeOther form './some_other'
export const App = ({}) => (
  <div>
   <SomeOther/>
 </div>
)
export default connect(...)(App)


// index.js
import ConnectedApp, {App} from './app'
...

let mockedStore = mockStore({})
...
const component = mount(<App/>, {
                context: {store: mockedStore},
                childContextTypes: {store: PropTypes.object.isRequired} // <- this will allow to pass store to child components which is required by 'connect'
});

Hope someone will have less pain

My issue was I was logging .html() use .debug() and everything's fine

@th3fallen you're doing God's work. Thanks! That did the trick for me.

I finally found a very good solution to get a wrapper from a decorated component. For shallowed components you can use dive() but there is no similar method for mounted components. This is the solution I did:

const wrapper = mount(shallow(<MyComponent />).get(0))

Works like a charm :)

@omakoleg Works like charm. One question though, Can we use Flow instead of prop-type in this case?

@omakoleg you are using mount and not shallow, thus you need a store for your child connected component, this is not related to this bug

import { mountWrap } from '../contextWrap'
import { Provider } from 'react-redux'
import sinon from 'sinon'
import Login from '../components/Login/'
// import makeStore from '../redux/createStore'

import React from 'react'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'

const mockStore = configureMockStore([ thunk ])
const authDetails = {
  'authDetails' : {
    Terms :''
  }
}

const match = {
  params : {}
}
let actionSpy = sinon.spy()
let actionHistorySpy = sinon.stub({})
let authDetails_ = sinon.stub(authDetails)
let store
let component
/* eslint-disable */
describe('tests for MyContainerComponent', () => {
  beforeEach(() => {
    store = mockStore(authDetails)
    component = mountWrap(<Provider store={ store }>
      <Login history={actionHistorySpy} match={match} setGlobalLoaderStatus= {actionSpy} userDetail={authDetails_} />
     </Provider>)
  })

  it('renders container', () => {
    console.log(component.debug())
  })
})

Here I have found, after long hunting of solution.

Was this page helpful?
0 / 5 - 0 ratings