143

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
foobar
  • 3,849
  • 8
  • 22
  • 32
  • How do you know it didn't do anything? What are you checking next to see if the button click occurred? – Toby May 02 '17 at 21:20
  • 1
    Good question. I expect the error field to appear: const field = pageMounted.find('#notification'); expect(field.length).toBe(1); – foobar May 02 '17 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? – Toby May 02 '17 at 21:23
  • Could you please add the code for the `MyCoolPage ` component, otherwise its hard to figure out whats the actual problem is. – Andreas Köberle May 03 '17 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 = ( ); 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! – foobar May 03 '17 at 20:48
  • Bro it't not Jest, it's enzymer.. let it clear its not Jest because you are making newbie in testing (like me) feel confused about the framework... Jest doesn't provide a "shallow" or "mount" functions. – Code Drop Sep 09 '22 at 21:02

9 Answers9

186

#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);
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Saman
  • 5,044
  • 3
  • 28
  • 27
  • 2
    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. – foobar Sep 14 '17 at 15:06
  • On the back of your first example, can you give an example of how we can write a test for an `onChange` function whose `value` matches the input element's `value` attribute? Thanks! – blankface Jan 10 '18 at 04:41
  • 18
    What does this actually test, though? – Omortis Apr 03 '19 at 15:02
  • 1
    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? – Jeremy Moritz Oct 25 '19 at 14:04
  • While it does answer the question for React, most of this answer is more related to mocking than it is to simulating a button click. – Brady Dowling Mar 31 '20 at 12:15
  • 1
    @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. – shan May 19 '20 at 15:48
  • 4
    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. – B-Lat May 27 '20 at 16:27
  • What would be a way to achieve the same thing using Jest and @testing-library/user-event? – Arijit Ghosh Jan 21 '22 at 09:21
41

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

Black
  • 9,541
  • 3
  • 54
  • 54
  • 1
    Which previous answer? Or is it more than one (which ones?)? – Peter Mortensen Sep 24 '20 at 18:03
  • 3
    @PeterMortensen I have clarified the answer. Accepted answer is using enzyme simulate, which is going to be deprecated. – Black Sep 24 '20 at 19:58
  • you might need to call `wrapper.update()` after one of these, as enzyme might not be able to notice that a change happened. – Hinrich Oct 05 '20 at 14:40
  • What about a button that does not have a `onClick` prop? Such as a button of `type="submit"` within a `
    `? 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.
    – Oli Nov 17 '20 at 16:43
  • 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(), }); }); ``` – nfroidure Jan 08 '21 at 10:55
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();
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jackgisel
  • 191
  • 2
  • 11
  • 20
    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. – Jeremy Moritz Oct 25 '19 at 14:07
  • 5
    @JeremyMoritz that is why I don't understand the point or the logic on unit tests. – user3808307 Jan 20 '20 at 16:13
12

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()
})
gerardnico
  • 855
  • 11
  • 10
2

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");
});
Mehdi Faraji
  • 2,574
  • 8
  • 28
  • 76
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();
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
utkarsh
  • 143
  • 4
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);
  });
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oleksii Trekhleb
  • 2,543
  • 20
  • 22
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);
    });
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0
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();
  });
});
Navid Barsalari
  • 195
  • 2
  • 5