Enzyme: simulate click "expected to have been called one time, but it was called two times"

Created on 27 Oct 2018  路  9Comments  路  Source: enzymejs/enzyme

Current behavior

I have a click handler on li's inside a ul:

import React, { Component } from "react";

class ItemList extends Component {
  constructor(props) {
    super(props);

    this.buildItems = this.buildItems.bind(this);
    this.delete = this.delete.bind(this);
  }

  buildItems(item) {
    return (
      <li onClick={() => this.delete(item.key)} key={item.key}>
        {item.text}
      </li>
    );
  }

  delete(key) {
    this.props.delete(key);
  }

  render() {
    let todoItems = this.props.items.map(this.buildItems);

    return (
      <div className="item-list-container">
        <ul className="item-list">{todoItems}</ul>
      </div>
    );
  }
}

export default ItemList;

test:

it("calls delete with correct key when item clicked", () => {
    const mockPropDelete = jest.fn();
    const deleteSpy = jest.spyOn(ItemList.prototype, "delete");
    let todoItems = [
      { key: 1234, text: "fold laundry" },
      { key: 4321, text: "mow lawn" }
    ];
    const wrapper = shallow(
      <ItemList items={todoItems} delete={mockPropDelete} />
    );
    const item = wrapper.find("li").at(1); //get item at index 1 since find 'li' will return more than 1 result

    item.simulate("click");

    expect(deleteSpy).toHaveBeenCalledTimes(1);
    expect(deleteSpy).toHaveBeenCalledWith(4321);
    expect(mockPropDelete).toHaveBeenCalledTimes(1);
  });

and I get the error _"Expected mock function to have been called one time, but it was called two times."_

Expected behavior

That my spy should only be called once. I feel like I'm missing something that I haven't been able to find searching around and I suspect this has to do with there being multiple li's.

invalid question

Most helpful comment

First off, thank you for your response and questions...I'm doing by best to teach myself React, Jest and Enzyme in my spare time outside of work so your questions are forcing me to look at things through a different lens which is absolutely appreciated! After reading through your questions it is clear that this component I wrote is overly complicated. My initial thought process was to pass in a function in this component's parent as a prop which would mutate the state. I created a "local" function within this component to handle the click and in turn call the function passed in as a prop...but obviously that is totally unnecessary and as you said, makes the code overly complicated. After refactoring the code to your example you provided and tweaking the tests it bit it is now working for me. So I guess my over-engineered solution was leading to side effects. Thank you again for helping me simplify this component and more importantly, helping me think through how to simplify this code because clearly, there was a better way.

All 9 comments

Works for me. No errors.
Your ItemList component refactored:

import React from 'react';

const ItemList = ({ items, deleteTodoItem }) => (
  <div className="item-list-container">
    <ul className="item-list">
      {items.map(({ text, key}) => (
        <li
          onClick={deleteTodoItem}
          key={key}
          data-key={key}
        >
          {text}
        </li>
      ))}
    </ul>
  </div>
);

export default ItemList;

Don't use delete as a method name. It's a reserved word in JasaScript. Moreover, it's unclear what you are trying to delete.
Your code has 3 handlers to delete todo item:

  • one that you pass as prop to ItemList component;
  • one more redundant handler that you create in your component as class method;
  • and one more that you pass to onClick property as arrow function with no args.

You can pass parameters using data attribute and access it in your event handler e.target.dataset.

As for me, class method that generates JSX is redundant. This approach makes your code overly complicated and difficult to test.

Regarding your test:
Don't use simulate - it is not recommended. Instead call handler directly.

First off, thank you for your response and questions...I'm doing by best to teach myself React, Jest and Enzyme in my spare time outside of work so your questions are forcing me to look at things through a different lens which is absolutely appreciated! After reading through your questions it is clear that this component I wrote is overly complicated. My initial thought process was to pass in a function in this component's parent as a prop which would mutate the state. I created a "local" function within this component to handle the click and in turn call the function passed in as a prop...but obviously that is totally unnecessary and as you said, makes the code overly complicated. After refactoring the code to your example you provided and tweaking the tests it bit it is now working for me. So I guess my over-engineered solution was leading to side effects. Thank you again for helping me simplify this component and more importantly, helping me think through how to simplify this code because clearly, there was a better way.

One other question for you @voliev is there a way for me to access e.target.dataset within my JSX? Something along the lines of

       <li
          onClick={deleteTodoItem(event.target.dataset)}
          key={key}
          data-key={key}
        >

This doesn't work, but what I'm trying to solve is a way to pass just the item's key to the deleteTodoItem function as an argument

@bkrainock pass a function - onClick={(e) => { whatever(); }}

@ljharb thank you! Looks like I needed to mock my event with a target and dataset object, but it looks like I'm up and running and all my tests are passing. Thanks for pointing me in the right direction!

Huh that's weird...my tests pass, but when I run it in the browser there is no onClick on my li's. Am I missing something???


import React from "react";

const ItemList = ({ items, deleteTodoItem }) => (
  <div className="item-list-container">
    <ul className="item-list">
      {items.map(({ text, key }) => (
        <li
          onClick={event => {
            deleteTodoItem(event.target.dataset);
          }}
          key={key}
          data-key={key}
        >
          {text}
        </li>
      ))}
    </ul>
  </div>
);

export default ItemList;

React doesn鈥檛 install event handlers directly on elements; you should debug with React dev tools, not directly on the dom.

@bkrainock, if you set arrow function as a handler, it doesn't make sense to pass the event object to deleteTodoItem unless you really need it. Just onClick={() => deleteTodoItem(key)} is enough in your case.
In this example onClick={deleteTodoItem(event.target.dataset)} you are calling deleteTodoItem first and passing its return value as onClick handler. This means that in this case your todos will be removed and you will get an empty list.

@voliev thank you again for your assistance, you truly have been a life saver today! I was trying to figure out why my filter wasn't working in my HOC when deleteTodoItem was being called with the key. Just using a plain arrow function that calls deleteTodoItem(key) works as expected, and I was able to get my tests all passing. Thank you for your help it's much appreciated!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

blainekasten picture blainekasten  路  3Comments

andrewhl picture andrewhl  路  3Comments

blainekasten picture blainekasten  路  3Comments

timhonders picture timhonders  路  3Comments

abe903 picture abe903  路  3Comments