Enzyme: TypeError: Attempted to wrap undefined property componentDidMount as function

Created on 8 Apr 2016  路  15Comments  路  Source: enzymejs/enzyme

Trying to test a component that fetches data in componentWillMount, calls setState() with the received data and renders a number of child components, like this:

export default class MyComponent extends React.Component {
    constructor() {
        super();
        this.state = {
            items: []
        };
    }

    componentWillMount() {
        Api.fetchItems().end((err, response) => {
            if (response && response.ok) {
                this.setState({ items: response.items });
            }
        });
    }

    render() {
        return (
            <div className="items-list">
                { this.state.items.map((item, i) =>
                    <Item item={ item } key={ i }/>
                ) }
            </div>
        );
    }
}

The test is very basic and almost 1to1 taken from the example:

import sinon from 'sinon';
import { expect } from 'chai';

it('mounts correctly', () => {
    sinon.spy(MyComponent.prototype, 'componentDidMount');
    const wrapper = mount(<MyComponent />);
    expect(MyComponent.prototype.componentDidMount.calledOnce).to.equal(true);
});

Unfortunately, the test fails:

TypeError: Attempted to wrap undefined property componentDidMount as function
      at Object.wrapMethod (node_modules/sinon/lib/sinon/util/core.js:106:29)
      at Object.spy (node_modules/sinon/lib/sinon/spy.js:41:26)
      at Context.<anonymous> (MyComponent.spec.js:21:19)

Any ideas? It seems the component doesn't even mount at all, adding a expect(wrapper.hasClass('items-list')).toBe(true) fails as well.

Most helpful comment

sinon can't stub over non-existent properties. If you add a componentDidMount() {} to your component, then it should work.

All 15 comments

Just for the record, spying on other methods works just as expected:

sinon.spy(Api, 'fetchItems');
mount(<MyComponent />);
expect(Api.fetchItems.calledOnce).to.be.true;

sinon can't stub over non-existent properties. If you add a componentDidMount() {} to your component, then it should work.

I think componentDidMount is a typo and @doque was indeed referring to componentWillMount, because I am getting the same error: TypeError: Attempted to wrap undefined property componentWillMount as function.

 sinon.spy(App.prototype, 'componentWillMount');

I am mounting this way:

const rendered = mount(<App />, { context: { store: store }});

I am testing a component wrapped in a Redux Provider and if I log the App.prototype, componentWillMount is simply not there:

Connect {
  shouldComponentUpdate: [Function: shouldComponentUpdate],
  computeStateProps: [Function: computeStateProps],
  configureFinalMapState: [Function: configureFinalMapState],
  computeDispatchProps: [Function: computeDispatchProps],
  configureFinalMapDispatch: [Function: configureFinalMapDispatch],
  updateStatePropsIfNeeded: [Function: updateStatePropsIfNeeded],
  updateDispatchPropsIfNeeded: [Function: updateDispatchPropsIfNeeded],
  updateMergedPropsIfNeeded: [Function: updateMergedPropsIfNeeded],
  isSubscribed: [Function: isSubscribed],
  trySubscribe: [Function: trySubscribe],
  tryUnsubscribe: [Function: tryUnsubscribe],
  componentDidMount: [Function: componentDidMount],
  componentWillReceiveProps: [Function: componentWillReceiveProps],
  componentWillUnmount: [Function: componentWillUnmount],
  clearCache: [Function: clearCache],
  handleChange: [Function: handleChange],
  getWrappedInstance: [Function: getWrappedInstance],
  render: [Function: render],
  componentWillUpdate: [Function: componentWillUpdate] }

The property is not there, so sinon is not seeing it. Am I missing something obvious? Is this even an Enzyme issue?

Looks like your <App /> is wrapped by a redux's connect HOC. The prototype you're seeing is for that. You would need to get the wrapped instanced from Connect or just import your component (without the connect() wrapper) and mock the props that redux is providing.

closing in favor of #472

@Aweary thanks for the reply! I am mocking what Redux gives me like this:

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

const middlewares = [thunk];
const mockStore = configureStore(middlewares);

...

store = mockStore(storeStateMock); // where storeStateMock is literally an object that mocks my state

Then mount the component as described above, passing the store as the context. Everything else works just fine - testing props and children component. The componentWillMount test doesn't work though :/

I am having same error, while the test looks pretty straight forward :/ anyone?

it('componentDidMount() should be called once', () => {
// Setup

    // Exercise
    const spy = sinon.spy(AddComment.prototype, 'componentDidMount');
    wrapper = mount(<AddComment />);

    // Verify
    expect(spy.calledOnce).to.equal(true);

    // Clean up
    spy.restore();
});

UPDATE: passed in spy as props and it works :)

const spy = sinon.spy(AddComment.prototype, 'componentDidMount');
wrapper = mount(<AddComment componentDidMount={spy} />);

@ignasBa that definitely shouldn't work unless your component has a bug. Can you share the implementation of AddComment?

@ljharb So yes, I am still struggling with it. The approach above only works when my componentDidMount() in AddComment looks like this:
componentDidMount () { this.setState({ id: this.props.id }) };

When I write it as arrow function:
componentDidMount = () => { this.setState({ id: this.props.id }) };
It stops working. Then I tried to change my test to:
`
// Setup
const instance = new AddComment();
spy = sinon.stub(instance, 'componentDidMount');

    wrapper = mount(<AddComment />);

    // Verify
    expect(spy.calledOnce).to.equal(true);`

It actually wraps my function, but test doesn't pass, spy does not get called :/

You don't want that to be an arrow function, that should only be a class method, and you should be spying on the prototype method.

@ljharb thanks! thats good to know. You know any good read what methods should be class, and which arrow?

@ignasBa any method that is included in the React Component API should be a class method.

@ignasBa nothing should be an arrow function or a bound function except a thin wrapper around a class method, for the purpose of binding to this or an argument. 100% of your significant code should be in instance methods, not own properties.

You can use arrow functions if you're using the property initializer syntax provided by the class properties babel transform.

Even then, it's a bad idea, because that creates an own property on every instance, instead of delegating the majority of the execution to the shared, optimizable, instance method on the prototype.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andrewhl picture andrewhl  路  3Comments

mattkauffman23 picture mattkauffman23  路  3Comments

ivanbtrujillo picture ivanbtrujillo  路  3Comments

heikkimu picture heikkimu  路  3Comments

benadamstyles picture benadamstyles  路  3Comments