devboell
devboell

Reputation: 1190

Trouble testing button onClick handler with Enzyme

I am having trouble using Enzyme's contains method when it comes to a button's onClick handler where the provided action method requires an argument. I ran into this while passing redux actions to a component, but I'll use simplified examples here.

Say you have two methods:

const logClick = () => console.log('button was clicked');
const logClickWithArg = (message) => console.log('button was clicked: ', message);

You pass them to a component, and in that component you have two buttons:

<button
  onClick={logClick}
>
  Click
</button>
<button
  onClick={() => logClickWithArg('hello')}
>
  Click With Arg
</button>

When I test the first button, there's no problem:

  expect(wrapper.contains(
    <button
      onClick={logClick}
    >
      Click
    </button>)).toBe(true);

it passes. However, the second:

  expect(wrapper.contains(
    <button
      onClick={() => logClickWithArg('hello')}
    >
      Click
    </button>)).toBe(true);

fails with the unhelpful output:

  expect(received).toBe(expected)

    Expected value to be (using ===):
      true
    Received:
      false

      at Object.<anonymous>.it (src/App.test.js:42:3)
      at process._tickCallback (internal/process/next_tick.js:103:7)

I've tried to find out more by trying all kinds of comparisons, like:

console.log('first ', wrapper.find('button').first().props().onClick);
console.log('last ', wrapper.find('button').last().props().onClick);
expect(wrapper.find('button').first().props().onClick).toEqual(logClick);
expect(wrapper.find('button').last().props().onClick).toEqual(logClickWithArg);

which results in:

  console.log src/App.test.js:29
    first  () => console.log('button was clicked')

  console.log src/App.test.js:30
    last  () => logClickWithArg('hello')

expect(received).toEqual(expected)

    Expected value to equal:
      [Function logClickWithArg]
    Received:
      [Function onClick]

I am using Jest as a test-runner, and encountered this in both the create-react-app and react-boilerplate set-up. Any idea what I am doing wrong?

EDIT

I'll give my own workaround in an answer below. I'll use my actual code there instead of these examples. However, I am still curious why the test here fails ....

Upvotes: 2

Views: 21128

Answers (2)

devboell
devboell

Reputation: 1190

(this is from a react-boilerplate project, using styled-components; the tests pass and coverage is 100%)

index.js

import React, { PropTypes } from 'react';

import Menu from './Menu';
import MenuItem from './MenuItem';

const QuizModeSelector = ({ quizMode, onSetQuizMode }) => (
  <Menu>
    <MenuItem
      onClick={() => onSetQuizMode('pc')}
      selected={quizMode === 'pc'}
    >
      Notes
    </MenuItem>
    <MenuItem
      onClick={() => onSetQuizMode('pitch')}
      selected={quizMode === 'pitch'}
    >
      Pitch
    </MenuItem>
  </Menu>
);

QuizModeSelector.propTypes = {
  quizMode: PropTypes.string,
  onSetQuizMode: PropTypes.func,
};

export default QuizModeSelector;

index.test.js

import React from 'react';
import { shallow } from 'enzyme';

import QuizModeSelector from '../index';
import Menu from '../Menu';
import MenuItem from '../MenuItem';

describe('<QuizModeSelector />', () => {
  const quizMode = 'pc';
  const onSetQuizMode = jest.fn();
  const props = {
    quizMode, onSetQuizMode,
  };

  const renderedComponent = shallow(<QuizModeSelector {...props} />);

  it('should render a <Menu> tag', () => {
    expect(renderedComponent.type()).toEqual(Menu);
  });

  it('should contain 2 MenuItems', () => {
    const items = renderedComponent.find(MenuItem);
    expect(items).toHaveLength(2);
  });

  it('should render a pc MenuItem', () => {
    expect(renderedComponent.containsMatchingElement(
      <MenuItem
        selected={quizMode === 'pc'}
      >
        Notes
      </MenuItem>
    )).toEqual(true);
  });

  it('should render a pitch MenuItem', () => {
    expect(renderedComponent.containsMatchingElement(
      <MenuItem
        selected={quizMode === 'pitch'}
      >
        Pitch
      </MenuItem>
    )).toEqual(true);
  });

  it('should handle click events', () => {
    renderedComponent.find(MenuItem).first().simulate('click');
    expect(onSetQuizMode).toHaveBeenCalledWith('pc');
    renderedComponent.find(MenuItem).last().simulate('click');
    expect(onSetQuizMode).toHaveBeenLastCalledWith('pitch');
  });
});

Upvotes: 1

luboskrnac
luboskrnac

Reputation: 24561

I would suggest to revise testing strategy.

You can test HTML rendering this way (using enzyme):

    // GIVEN
    const expectedNode = shallow(
        <div>
            <button className="simple-button">Click</button>
            <button>Click With Arg</button>
        </div>
    );

    // WHEN
    const actualNode = shallow(<YourComponentName />);

    // THEN
    expect(actualNode.html()).to.equal(expectedNode.html());

and component interactivity this way (using enzyme and sinon):

    // GIVEN
    const clickCallback = sinon.spy();
    const actualNode = shallow(<YourComponentName onClick={clickCallback}/>);

    // WHEN
    actualNode.find(".simple-button").simulate("click");

    // THEN
    sinon.assert.called(clickCallback);

As you are using Jest, you may consider using Jest Snapshots for HTML verification.

Upvotes: 4

Related Questions