Enzyme: Mocked function is called twice, should be called once. Don't see why.

Created on 14 Feb 2019  路  3Comments  路  Source: enzymejs/enzyme

I'm trying to learn Jest and Enzyme but I'm having a problem I can't find a solution to, this is my test.. it's not very good I know but I'm learning:

    import * as apiMock from '../api';

    const fakePostId = '1';
    const fakePersona = 'Fake';

    jest.mock('../api', () => {
        return {
            fetchAllComments: jest.fn(() => {
                return [];
            }),
            filterComments: jest.fn(() => {
                return [];
            }),
            createCommentObject: jest.fn(() => {
                return [];
            }),
        };
    });

    test('checks if functions are called after didMount', () => {
        const component = shallow(
            <Comments postId={fakePostId} currentPersona={fakePersona} />
        );

        const spySetComments = jest.spyOn(
            component.instance(),
            'setCommentsFromLocalStorage'
        );

        component.instance().componentDidMount();

        expect(spySetComments).toHaveBeenCalledTimes(1);

        //Don't know why these are called 2! times, I can't see why removing componentDidMount makes it 0.
        expect(apiMock.fetchAllComments).toHaveBeenCalledTimes(1);
        expect(apiMock.filterComments).toHaveBeenCalledTimes(1);
    }

The problem is toHaveBeenCalledTimes(1) fails with reason:

Expected mock function to have been called one time, but it was called
two times.

But I don't know why. I guess it's because componentDidMount runs two times but I don't know how to solve it.
setCommentsFromLocalStorage only runs once since spy was created after, I can't create it before using prototype version.

ReactComponent looks like this:

    import React, { Component } from 'react';
    import PropTypes from 'prop-types';
    import CreateNewComment from './CreateNewComment';
    import SingleComment from './SingleComment';
    import * as api from '../api';

    class Comments extends Component {
      state = {
        comments: []
      };

      componentDidMount() {
        this.setCommentsFromLocalStorage();
      }

      setCommentsFromLocalStorage = (postId = this.props.postId) => {
        const fetchedComments = api.fetchAllComments();
        const comments = api.filterComments(fetchedComments, postId);
        this.setState({ comments });
      };

      removeComment = commentId => {
        api.removeComment(commentId);
        this.setCommentsFromLocalStorage();
      };

      renderCommentList = (comments, currentPersona) =>
        comments.map(comment => (
          <SingleComment
            {...comment}
            currentPersona={currentPersona}
            key={comment.id}
            onClick={this.removeComment}
          />
        ));

      render() {
        return (
          <div className="py-2">
            <h2 className="text-indigo-darker border-b mb-4">Comments</h2>
            {this.renderCommentList(this.state.comments, this.props.currentPersona)}
            <CreateNewComment
              postId={this.props.postId}
              author={this.props.currentPersona}
              updateComments={this.setCommentsFromLocalStorage}
            />
          </div>
        );
      }
    }

    Comments.propTypes = {
      postId: PropTypes.string.isRequired,
      currentPersona: PropTypes.string.isRequired
    };

    export default Comments;
question

Most helpful comment

It's because you call componentDidMount() twice:

  • first time in `shallow()
  • second time in component.instance().componentDidMount(); => remove this

componentDidMount() runs automatically when calling shallow:

As of Enzyme v3, the shallow API does call React lifecycle methods such as componentDidMount and componentDidUpdate

https://airbnb.io/enzyme/docs/guides/migration-from-2-to-3.html#lifecycle-methods

All 3 comments

The problem is that you have arrow functions in class fields; never do that. Instead, use a normal instance methods, and either constructor-bind or field-bind them, and then spy on the prototype before creating your wrappers.

It's because you call componentDidMount() twice:

  • first time in `shallow()
  • second time in component.instance().componentDidMount(); => remove this

componentDidMount() runs automatically when calling shallow:

As of Enzyme v3, the shallow API does call React lifecycle methods such as componentDidMount and componentDidUpdate

https://airbnb.io/enzyme/docs/guides/migration-from-2-to-3.html#lifecycle-methods

Seems answered.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

heikkimu picture heikkimu  路  3Comments

ahuth picture ahuth  路  3Comments

rexonms picture rexonms  路  3Comments

modemuser picture modemuser  路  3Comments

aweary picture aweary  路  3Comments