150

Simulating a button click seems like a very easy/standard operation. Yet, I can't get it to work in Jest.js tests.

This is what I tried (and also doing it using jQuery), but it didn't seem to trigger anything:

import { mount } from 'enzyme';

page = <MyCoolPage />;
pageMounted = mount(page);

const button = pageMounted.find('#some_button');
expect(button.length).toBe(1); // It finds it alright
button.simulate('click'); // Nothing happens
6
  • How do you know it didn't do anything? What are you checking next to see if the button click occurred? Commented May 2, 2017 at 21:20
  • 1
    Good question. I expect the error field to appear: const field = pageMounted.find('#notification'); expect(field.length).toBe(1); Commented May 2, 2017 at 21:21
  • Hrm. Have you added a console.warn to the function that runs onClick to see if it fires in the Jest console? Commented May 2, 2017 at 21:23
  • Could you please add the code for the MyCoolPage component, otherwise its hard to figure out whats the actual problem is. Commented May 3, 2017 at 19:28
  • 1
    Thank you guys for the tips. I found my problem, thanks to your questions. I basically did a small test with a simple button and it worked: MyCoolPage = ( <button type="submit" id="cool_button" onClick={() => { console.warn('I was clicked');}>Cool Button</button> ); I then realized that my button belonged to the redux-form, so it didn't have onClick, but onSubmit instead, so adding button.simulate('submit'); solved the issue. Thanks again for your feedback! Commented May 3, 2017 at 20:48

9 Answers 9

188

#1 Using Jest

This is how I use the Jest mock callback function to test the click event:

import React from 'react';
import { shallow } from 'enzyme';
import Button from './Button';

describe('Test Button component', () => {
  it('Test click event', () => {
    const mockCallBack = jest.fn();

    const button = shallow((<Button onClick={mockCallBack}>Ok!</Button>));
    button.find('button').simulate('click');
    expect(mockCallBack.mock.calls.length).toEqual(1);
  });
});

I am also using a module called enzyme. Enzyme is a testing utility that makes it easier to assert and select your React Components

#2 Using Sinon

Also, you can use another module called Sinon which is a standalone test spy, stubs and mocks for JavaScript. This is how it looks:

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

import Button from './Button';

describe('Test Button component', () => {
  it('simulates click events', () => {
    const mockCallBack = sinon.spy();
    const button = shallow((<Button onClick={mockCallBack}>Ok!</Button>));

    button.find('button').simulate('click');
    expect(mockCallBack).toHaveProperty('callCount', 1);
  });
});

#3 Using Your own Spy

Finally, you can make your own naive spy (I don't recommend this approach unless you have a valid reason for that).

function MySpy() {
  this.calls = 0;
}

MySpy.prototype.fn = function () {
  return () => this.calls++;
}

it('Test Button component', () => {
  const mySpy = new MySpy();
  const mockCallBack = mySpy.fn();

  const button = shallow((<Button onClick={mockCallBack}>Ok!</Button>));

  button.find('button').simulate('click');
  expect(mySpy.calls).toEqual(1);
});
Sign up to request clarification or add additional context in comments.

8 Comments

Thank you for a detailed answer Saman! This is very useful when you can pass onClick method directly into the component you are testing, and I will use your code as a reference for that :). I think in my example though I couldn't really pass onClick and I had to rely on other clues to know that the button was clicked.
What does this actually test, though?
I have a button that calls my handleClick method when clicked. How do i test that handleClick was actually called when the button is clicked?
@Saman Shafigh how would this work if lets say the button is nested two levels down? So the click handler is being passed from first component to 2nd component, then eventually to the button.
Aren't all of the above examples just testing that HTML works? i.e. if i create a button and assign it a click event, it will call that click event? It's not unit testing anything specific to our code.
|
43

Solutions in accepted answer are being deprecated

#4 Calling prop directly

Enzyme simulate is supposed to be removed in version 4. The main maintainer is suggesting directly invoking prop functions, which is what simulate does internally. One solution is to directly test that invoking those props does the right thing; or you can mock out instance methods, test that the prop functions call them, and unit test the instance methods.

You could call click, for example:

wrapper.find('Button').prop('onClick')() 

Or

wrapper.find('Button').props().onClick() 

Information about deprecation: Deprecation of .simulate() #2173

5 Comments

Which previous answer? Or is it more than one (which ones?)?
@PeterMortensen I have clarified the answer. Accepted answer is using enzyme simulate, which is going to be deprecated.
you might need to call wrapper.update() after one of these, as enzyme might not be able to notice that a change happened.
What about a button that does not have a onClick prop? Such as a button of type="submit" within a <form />? Yes could call onSubmit on the form - but this isn't ideal. Users will click the button, and that is what you want to test.
Just in case someone hits the same issue, if you have to handle the event, this can be usefull: ``` lang-js act(() => { component.root.findByType('button').props.onClick({ preventDefault: jest.fn(), stopPropagation: jest.fn(), }); }); ```
14

Testing-library makes this easy for you with the click function.

It's part of the user-event library that can be used with every dom environment (react, jsdom, browser, ...)

The example from the doc:

import React from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('click', () => {
  render(
    <div>
      <label htmlFor="checkbox">Check</label>
      <input id="checkbox" type="checkbox" />
    </div>,
  )

  userEvent.click(screen.getByText('Check'))
  expect(screen.getByLabelText('Check')).toBeChecked()
})

2 Comments

The question asked regarding a button not a checkbox
It does not matter as you can click all elements. Just change the label from the example with a button tag and it will work the same.
13

Using Jest, you can do it like this:

test('it calls start logout on button click', () => {
    const mockLogout = jest.fn();
    const wrapper = shallow(<Component startLogout={mockLogout}/>);
    wrapper.find('button').at(0).simulate('click');
    expect(mockLogout).toHaveBeenCalled();
});

2 Comments

What is the value in creating a complete button within your tests with a mocked callback when clicked and then clicking that button in the test? Like most testing examples I've seen, you haven't even tested a single line of your actual code when you do this.
@JeremyMoritz that is why I don't understand the point or the logic on unit tests.
4

I always test buttons with fireEvent :

import { fireEvent } from "@testing-library/react";

it("Button onClick", async () => {
    const handleOnClick = jest.fn();

    const { getByTestId } = render(<Button onClick={handleOnClick} />);
    const element = getByTestId("button");

    fireEvent.click(element);

    expect(handleOnClick).toBeCalled();
    expect(element).toHaveClass("animate-wiggle");
});

Comments

1

You may use something like this to call the handler written on click:

import { shallow } from 'enzyme'; // Mount is not required

page = <MyCoolPage />;
pageMounted = shallow(page);

// The below line will execute your click function
pageMounted.instance().yourOnClickFunction();

Comments

1

Additionally to the solutions that were suggested in sibling comments, you may change your testing approach a little bit and test not the whole page all at once (with a deep children components tree), but do an isolated component testing. This will simplify testing of onClick() and similar events (see example below).

The idea is to test only one component at a time and not all of them together. In this case all children components will be mocked using the jest.mock() function.

Here is an example of how the onClick() event may be tested in an isolated SearchForm component using Jest and react-test-renderer.

import React from 'react';
import renderer from 'react-test-renderer';
import { SearchForm } from '../SearchForm';

describe('SearchForm', () => {
  it('should fire onSubmit form callback', () => {
    // Mock search form parameters.
    const searchQuery = 'kittens';
    const onSubmit = jest.fn();

    // Create test component instance.
    const testComponentInstance = renderer.create((
      <SearchForm query={searchQuery} onSearchSubmit={onSubmit} />
    )).root;

    // Try to find submit button inside the form.
    const submitButtonInstance = testComponentInstance.findByProps({
      type: 'submit',
    });
    expect(submitButtonInstance).toBeDefined();

    // Since we're not going to test the button component itself
    // we may just simulate its onClick event manually.
    const eventMock = { preventDefault: jest.fn() };
    submitButtonInstance.props.onClick(eventMock);

    expect(onSubmit).toHaveBeenCalledTimes(1);
    expect(onSubmit).toHaveBeenCalledWith(searchQuery);
  });
});

Comments

1

I needed to do a little bit of testing myself of a button component. These tests work for me ;-)

import { shallow } from "enzyme";
import * as React from "react";
import Button from "../button.component";

describe("Button Component Tests", () => {
    it("Renders correctly in DOM", () => {
        shallow(
            <Button text="Test" />
        );
    });
    it("Expects to find button HTML element in the DOM", () => {
        const wrapper = shallow(<Button text="test"/>)
        expect(wrapper.find('button')).toHaveLength(1);
    });

    it("Expects to find button HTML element with className test in the DOM", () => {
        const wrapper = shallow(<Button className="test" text="test"/>)
        expect(wrapper.find('button.test')).toHaveLength(1);
    });

    it("Expects to run onClick function when button is pressed in the DOM", () => {
        const mockCallBackClick = jest.fn();
        const wrapper = shallow(<Button onClick={mockCallBackClick} className="test" text="test"/>);
        wrapper.find('button').simulate('click');
        expect(mockCallBackClick.mock.calls.length).toEqual(1);
    });
});

Comments

1
import React from "react";
import { shallow } from "enzyme";
import Button from "../component/Button/Button";

describe("Test Button component", () => {
  let container = null;
  let clickFn = null;

  beforeEach(() => {
    clickFn = jest.fn();
    container = shallow(<Button buttonAction={clickFn} label="send" />);
  });
  it("button Clicked", () => {
    container.find("button").simulate("click");
    expect(clickFn).toHaveBeenCalled();
  });
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.