使用酶测试按钮 onClick 处理程序时遇到问题

Posted

技术标签:

【中文标题】使用酶测试按钮 onClick 处理程序时遇到问题【英文标题】:Trouble testing button onClick handler with Enzyme 【发布时间】:2017-07-02 16:51:40 【问题描述】:

当涉及到按钮的 onClick 处理程序时,我在使用 Enzyme 的 contains 方法时遇到问题,其中提供的操作方法需要一个参数。 我在将 redux 操作传递给组件时遇到了这个问题,但我将在这里使用简化的示例。

假设你有两种方法:

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

您将它们传递给一个组件,并且在该组件中您有两个按钮:

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

当我测试第一个按钮时,没有问题:

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

它通过了。但是,第二个:

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

因无用的输出而失败:

  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)

我尝试通过各种比较来了解更多信息,例如:

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

导致:

  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]

我使用 Jest 作为测试运行程序,并且在 create-react-app 和 react-boilerplate 设置中都遇到了这个问题。 知道我做错了什么吗?

编辑

我将在下面的答案中给出我自己的解决方法。我将在那里使用我的实际代码而不是这些示例。不过,我还是很好奇为什么这里的测试失败了....

【问题讨论】:

这个话题不能解决你的问题? ***.com/questions/35478076/… 【参考方案1】:

我建议修改测试策略。

您可以通过这种方式测试 html 渲染(使用 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());

以这种方式与组件交互(使用enzymesinon):

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

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

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

当您使用 Jest 时,您可以考虑使用Jest Snapshots 进行 HTML 验证。

【讨论】:

我明白你的意思,但我想知道在使用 react-boilerplate 和 styled-components 时它有多实用。 F.I.生成了类名,我似乎编写了不太明确的 html,而是嵌套了组件。我的意思是,这是可行的,但我觉得将组件与“包含”进行比较更方便。 "而且我似乎写的 html 不太明确,而是嵌套组件" -> 不明白这个 想不出样式组件和反应样板的任何问题。 是的,生成的类名可能是此类 HTML 测试的问题。 使用 styled-components 你倾向于用 f.i. 生成 html:export const Button = styled.button`` 而不是在你的 jsx 中手动写出来。您只需导入 Button 组件。【参考方案2】:

(这是来自 react-boilerplate 项目,使用样式化组件;测试通过且覆盖率为 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');
  );
);

【讨论】:

以上是关于使用酶测试按钮 onClick 处理程序时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

使用酶测试多个类名

MFC 按钮 onclick 处理程序

在“if”语句中没有调用模拟函数 - 用玩笑和酶反应应用程序测试?

空闲资源忙时测试 UI

如何使用酶、笑话和反应测试材料设计事件监听器

酶测试认证高阶组件(HOC)