React 测试:React 浅渲染单元测试中的事件处理程序
React Testing: Event handlers in React Shallow Rendering unit tests
背景
我正在尝试学习如何使用 React 浅渲染 TestUtil 并让测试通过,直到我向两者添加了一个 onClick
事件处理程序;似乎我在 Accordion.test.js
中尝试使用的 Accordion.toggle
函数与 Accordian.js
中的 this.toggle
函数肯定有一些区别......但我无法弄清楚.
问题
如何才能通过 Accordian.test.js
中突出显示的两个测试?
重现步骤
- 克隆https://github.com/trevordmiller/shallow-rendering-testing-playground
npm install
npm run dev
- 当您单击 "Lorem Ipsum" 时看到该组件正在工作
npm run test:watch
- 看到测试失败
要测试像 onClick
这样的用户事件,您必须使用 TestUtils.Simulate.click
。 Sadly:
Right now it is not possible to use ReactTestUtils.Simulate with Shallow rendering and i think the issue to follow should be: https://github.com/facebook/react/issues/1445
有许多问题阻止您的测试通过。
正在看测试"should be inactive by default":
Accordion.toggle
在您的测试中是 属性 的 Accordion
class,而 this.toggle
在您的代码中是 属性 Accordion
class 的实例 - 因此在这种情况下,您正在比较两个不同的事物。要在测试中访问 'instance' 方法,您可以将 Accordion.toggle
替换为 Accordion.prototype.toggle
。如果它不是 this.toggle = this.toggle.bind(this);
在你的构造函数中,它会起作用。这就引出了第二点。
当您在函数上调用 .bind() 时,它会在运行时创建一个新函数 - 因此您无法将它与原始函数进行比较 Accordion.prototype.toggle
。解决此问题的唯一方法是从 render:
的结果中提取 "bound" 函数
let toggle = result.props.children[0].props.onClick;
assert.deepEqual(result.props.children, [
<a onClick={toggle}>This is a summary</a>,
<p style={{display: 'none'}}>This is some details</p>
]);
关于你第二次失败的测试"should become active when clicked":
您尝试调用不存在的 result.props.onClick()
。你打算打电话给 result.props.children[0].props.onClick();
React 中存在一个错误,当使用浅层渲染调用 setState 时需要声明一个全局 "document" 变量 - 如何在每种情况下解决这个问题超出了本文的范围问题,但是让测试通过的快速解决方法是在调用 onClick 方法之前添加 global.document = {};
。换句话说,您的原始测试有:
result.props.onClick();
现在应该说:
global.document = {};
result.props.children[0].props.onClick();
参见 "Fixing Broken setState()" on this page and this react issue.
部分
Marcin Grzywaczewski wrote a great article 带有用于测试适用于浅渲染的点击处理程序的解决方法。
给定一个带有 onClick
属性的嵌套元素和一个上下文绑定到组件的处理程序:
render() {
return (
<div>
<a className="link" href="#" onClick={this.handleClick}>
{this.state.linkText}
</a>
<div>extra child to make props.children an array</div>
</div>
);
}
handleClick(e) {
e.preventDefault();
this.setState({ linkText: 'clicked' });
}
您可以手动调用 onClick
属性的函数值,存入事件对象:
it('updates link text on click', () => {
let tree, link, linkText;
const renderer = TestUtils.createRenderer();
renderer.render(<MyComponent />);
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
// initial state set in constructor
expect(linkText).to.equal('Click Me');
// manually invoke onClick handler via props
link.props.onClick({ preventDefault: () => {} });
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
expect(linkText).to.equal('Clicked');
});
我已经成功地测试了我在无状态组件中的点击。方法如下:
我的组件:
import './ButtonIcon.scss';
import React from 'react';
import classnames from 'classnames';
const ButtonIcon = props => {
const {icon, onClick, color, text, showText} = props,
buttonIconContainerClass = classnames('button-icon-container', {
active: showText
});
return (
<div
className={buttonIconContainerClass}
onClick={onClick}
style={{borderColor: color}}>
<div className={`icon-container ${icon}`}></div>
<div
className="text-container"
style={{display: showText ? '' : 'none'}}>{text}</div>
</div>
);
}
ButtonIcon.propTypes = {
icon: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired,
color: React.PropTypes.string,
text: React.PropTypes.string,
showText: React.PropTypes.bool
}
export default ButtonIcon;
我的测试:
it('should call onClick prop when clicked', () => {
const iconMock = 'test',
clickSpy = jasmine.createSpy(),
wrapper = ReactTestUtils.renderIntoDocument(<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>);
const component = findDOMNode(wrapper).children[0];
ReactTestUtils.Simulate.click(component);
expect(clickSpy).toHaveBeenCalled();
expect(component).toBeDefined();
});
重要的是包装组件:
<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>
希望对您有所帮助!
背景
我正在尝试学习如何使用 React 浅渲染 TestUtil 并让测试通过,直到我向两者添加了一个 onClick
事件处理程序;似乎我在 Accordion.test.js
中尝试使用的 Accordion.toggle
函数与 Accordian.js
中的 this.toggle
函数肯定有一些区别......但我无法弄清楚.
问题
如何才能通过 Accordian.test.js
中突出显示的两个测试?
重现步骤
- 克隆https://github.com/trevordmiller/shallow-rendering-testing-playground
npm install
npm run dev
- 当您单击 "Lorem Ipsum" 时看到该组件正在工作
npm run test:watch
- 看到测试失败
要测试像 onClick
这样的用户事件,您必须使用 TestUtils.Simulate.click
。 Sadly:
Right now it is not possible to use ReactTestUtils.Simulate with Shallow rendering and i think the issue to follow should be: https://github.com/facebook/react/issues/1445
有许多问题阻止您的测试通过。
正在看测试"should be inactive by default":
Accordion.toggle
在您的测试中是 属性 的Accordion
class,而this.toggle
在您的代码中是 属性Accordion
class 的实例 - 因此在这种情况下,您正在比较两个不同的事物。要在测试中访问 'instance' 方法,您可以将Accordion.toggle
替换为Accordion.prototype.toggle
。如果它不是this.toggle = this.toggle.bind(this);
在你的构造函数中,它会起作用。这就引出了第二点。当您在函数上调用 .bind() 时,它会在运行时创建一个新函数 - 因此您无法将它与原始函数进行比较
的结果中提取 "bound" 函数Accordion.prototype.toggle
。解决此问题的唯一方法是从 render:let toggle = result.props.children[0].props.onClick; assert.deepEqual(result.props.children, [ <a onClick={toggle}>This is a summary</a>, <p style={{display: 'none'}}>This is some details</p> ]);
关于你第二次失败的测试"should become active when clicked":
您尝试调用不存在的
result.props.onClick()
。你打算打电话给result.props.children[0].props.onClick();
React 中存在一个错误,当使用浅层渲染调用 setState 时需要声明一个全局 "document" 变量 - 如何在每种情况下解决这个问题超出了本文的范围问题,但是让测试通过的快速解决方法是在调用 onClick 方法之前添加
global.document = {};
。换句话说,您的原始测试有:result.props.onClick();
现在应该说:
global.document = {}; result.props.children[0].props.onClick();
参见 "Fixing Broken setState()" on this page and this react issue.
部分
Marcin Grzywaczewski wrote a great article 带有用于测试适用于浅渲染的点击处理程序的解决方法。
给定一个带有 onClick
属性的嵌套元素和一个上下文绑定到组件的处理程序:
render() {
return (
<div>
<a className="link" href="#" onClick={this.handleClick}>
{this.state.linkText}
</a>
<div>extra child to make props.children an array</div>
</div>
);
}
handleClick(e) {
e.preventDefault();
this.setState({ linkText: 'clicked' });
}
您可以手动调用 onClick
属性的函数值,存入事件对象:
it('updates link text on click', () => {
let tree, link, linkText;
const renderer = TestUtils.createRenderer();
renderer.render(<MyComponent />);
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
// initial state set in constructor
expect(linkText).to.equal('Click Me');
// manually invoke onClick handler via props
link.props.onClick({ preventDefault: () => {} });
tree = renderer.getRenderOutput();
link = tree.props.children[0];
linkText = link.props.children;
expect(linkText).to.equal('Clicked');
});
我已经成功地测试了我在无状态组件中的点击。方法如下:
我的组件:
import './ButtonIcon.scss';
import React from 'react';
import classnames from 'classnames';
const ButtonIcon = props => {
const {icon, onClick, color, text, showText} = props,
buttonIconContainerClass = classnames('button-icon-container', {
active: showText
});
return (
<div
className={buttonIconContainerClass}
onClick={onClick}
style={{borderColor: color}}>
<div className={`icon-container ${icon}`}></div>
<div
className="text-container"
style={{display: showText ? '' : 'none'}}>{text}</div>
</div>
);
}
ButtonIcon.propTypes = {
icon: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func.isRequired,
color: React.PropTypes.string,
text: React.PropTypes.string,
showText: React.PropTypes.bool
}
export default ButtonIcon;
我的测试:
it('should call onClick prop when clicked', () => {
const iconMock = 'test',
clickSpy = jasmine.createSpy(),
wrapper = ReactTestUtils.renderIntoDocument(<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>);
const component = findDOMNode(wrapper).children[0];
ReactTestUtils.Simulate.click(component);
expect(clickSpy).toHaveBeenCalled();
expect(component).toBeDefined();
});
重要的是包装组件:
<div><ButtonIcon icon={iconMock} onClick={clickSpy} /></div>
希望对您有所帮助!