使用酶无法测试按钮 onClick 处理程序
Trouble testing button onClick handler with Enzyme
当涉及按钮的 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 设置中都遇到了这个问题。
知道我做错了什么吗?
编辑
我将在下面的回答中给出我自己的解决方法。我将在那里使用我的实际代码而不是这些示例。不过我还是很好奇这里为什么测试失败....
我建议修改测试策略。
您可以用这种方式测试 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());
和组件交互方式(使用 enzyme
和 sinon
):
// 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;测试通过且覆盖率为 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 处理程序时,我在使用 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 设置中都遇到了这个问题。 知道我做错了什么吗?
编辑
我将在下面的回答中给出我自己的解决方法。我将在那里使用我的实际代码而不是这些示例。不过我还是很好奇这里为什么测试失败....
我建议修改测试策略。
您可以用这种方式测试 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());
和组件交互方式(使用 enzyme
和 sinon
):
// 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;测试通过且覆盖率为 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');
});
});