在Jest中模拟按钮点击

145

模拟按钮点击似乎是一个非常简单/标准的操作。然而,在 Jest.js 测试中,我无法让它工作。

这是我尝试过的(也使用 jQuery 进行操作),但似乎没有触发任何事件:

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

你怎么知道它什么都没做?接下来你会检查什么以确定按钮点击是否发生了? - Toby
1
好问题。我期望出现错误字段:const field = pageMounted.find('#notification'); expect(field.length).toBe(1); - foobar
嗯,你是否已经在运行onClick函数的地方添加了console.warn,以查看它是否在Jest控制台中触发? - Toby
请问您能否添加 MyCoolPage 组件的代码,否则很难确定实际问题所在。 - Andreas Köberle
1
谢谢大家的建议。多亏了你们的问题,我找到了问题所在。我基本上用一个简单的按钮进行了小测试,它起作用了:MyCoolPage = ( <button type="submit" id="cool_button" onClick={() => { console.warn('I was clicked');}>Cool Button</button> ); 然后我意识到我的按钮属于redux-form,所以它没有onClick,而是有onSubmit,所以添加button.simulate('submit');解决了这个问题。再次感谢您的反馈! - foobar
兄弟,这不是Jest,而是Enzyme...让我们澄清一下,这不是Jest,因为你让测试中的新手(比如我)对框架感到困惑... Jest没有提供“shallow”或“mount”函数。 - Code Drop
9个回答

187

#1 使用 Jest

以下是我如何使用 Jest 模拟回调函数来测试点击事件:

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);
  });
});

我还使用一个名为enzyme的模块。Enzyme是一个测试实用工具,它可以更轻松地断言和选择你的React组件。

#2 使用Sinon

此外,您还可以使用另一个名为Sinon的模块,它是 JavaScript 的独立测试 spy、stub 和 mock。这是它的外观:

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 使用自己的间谍

最后,您可以制作自己的天真间谍(除非您有充分的理由,否则我不建议采用此方法)。

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);
});

2
谢谢你详细的回答,Saman!当你可以直接将onClick方法传递到你正在测试的组件中时,这非常有用,我会把你的代码作为参考 : )。不过,在我的例子中,我似乎无法直接传递onClick,所以我必须依靠其他线索来知道按钮是否被点击了。 - foobar
18
这到底测试的是什么? - Omortis
1
当我点击按钮时,它会调用我的 handleClick 方法。我该如何测试当按钮被点击时 handleClick 是否真的被调用了? - Jeremy Moritz
1
@Saman Shafigh 如果按钮嵌套两个级别下面,那么这个方法该怎么用呢?所以单击处理程序是从第一个组件传递到第二个组件,最终传递到按钮上。 - shan
4
上述所有的例子都只是测试 HTML 是否可以正常工作,对我们的代码没有进行具体的单元测试。例如,如果我创建一个按钮并为其分配单击事件,它将调用该单击事件,但这并没有针对我们的代码进行任何特定的单元测试。 - B-Lat
显示剩余3条评论

43

已被接受的答案中的解决方案已经过时

#4 直接调用prop

Enzyme simulate在版本4中应该被移除。主要维护者建议直接调用prop函数,这就是simulate内部所做的。一种解决方法是直接测试调用这些props是否正确;或者您可以模拟实例方法,测试prop函数是否调用了它们,并对实例方法进行单元测试。

例如,您可以调用click:

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

或者

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

关于弃用的信息: .simulate()方法被弃用 #2173


1
哪个之前的回答?或者是多个(哪些)? - Peter Mortensen
3
@PeterMortensen,我已经澄清了答案。被接受的答案正在使用酶模拟,这将被弃用。 - Black
1
在这些操作之后,您可能需要调用 wrapper.update(),因为 enzyme 可能无法注意到发生的更改。 - Hinrich
一个没有 onClick 属性的按钮怎么办?比如在 <form /> 中的 type="submit" 按钮?是的,可以在表单上调用 onSubmit - 但这并不理想。用户会点击按钮,这就是你想要测试的。 - Oli
以防万一,如果有人遇到相同的问题,如果您需要处理事件,则这可能很有用: act(() => { component.root.findByType('button').props.onClick({ preventDefault: jest.fn(), stopPropagation: jest.fn(), }); });(请注意,这是计算机编程术语,翻译时需要根据上下文进行适当的翻译) - nfroidure

13

使用 Jest,你可以像这样做:

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();
});

20
在测试中创建一个完整的按钮,并模拟在点击时调用回调函数,之后在测试中点击该按钮,这样做有什么价值?和我看到的大多数测试示例一样,当你这样做时,实际代码中甚至没有测试过一行。 - Jeremy Moritz
5
@JeremyMoritz这就是为什么我不理解单元测试的重点或逻辑。 - user3808307

12

使用click函数可以轻松实现Testing-library的这一功能。

它是user-event库的一部分,可用于每个dom环境(react、jsdom、browser等)。

以下是文档中的示例:

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
问题是关于按钮而不是复选框。 - Carmine Tambascia
3
无论哪个元素,都可以单击。只需将示例中的标签更改为按钮标签即可正常工作。 - gerardnico

4

我总是使用 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");
});

1
你可以使用类似以下的代码来调用点击事件处理程序:
import { shallow } from 'enzyme'; // Mount is not required

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

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

1
此外,除了前面评论中提出的解决方案之外,您还可以稍微改变您的测试方法,不是一次性地测试整个页面(包括深层子组件树),而是进行隔离的组件测试。这将简化对onClick()等事件的测试(见下面的示例)。
思路是一次只测试一个组件,而不是所有组件都一起测试。在这种情况下,使用jest.mock()函数来模拟所有子组件。
以下是一个示例,演示如何使用Jestreact-test-renderer在隔离的SearchForm组件中测试onClick()事件。
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);
  });
});

1

我需要自己对按钮组件进行一些测试。这些测试对我来说有效;-)

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);
    });
});

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();
  });
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接