我在React应用程序中有一个相当基本的反应组件。我想测试一下"提交的#34;提交表单时,状态的一部分从false变为true。不是特别难。但酶测试似乎无法找到按钮。不确定它是否与if / else语句有关。
以下是组件:
import React from 'react';
import { connect } from 'react-redux';
import { questionSubmit } from '../actions/users';
import { getCurrentUser, clearMessage } from '../actions/auth';
export class AnswerForm extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
}
}
handleFormSubmit(event) {
event.preventDefault();
this.setState({ submitted: true });
this.props.dispatch(questionSubmit(this.answerInput.value, this.props.currentUsername));
this.answerInput.value = '';
}
handleNextButton() {
this.setState({ submitted: false });
this.props.dispatch(getCurrentUser(this.props.currentUsername))
}
render() {
let nextButton;
let form;
let message = <p>{this.props.message}</p>
if (this.state.submitted) {
nextButton = <button className="button-next" onClick={() => this.handleNextButton()}>Next</button>;
}
else {
form =
<form onSubmit={e => this.handleFormSubmit(e)}>
<input className="input-answer" ref={input => this.answerInput = input}
placeholder="Your answer" />
<button id="button-answer" type="submit">Submit</button>
</form>;
}
return (
<div>
<p>{this.props.message}</p>
{form}
{nextButton}
</div>
)
}
}
export const mapStateToProps = (state, props) => {
return {
message: state.auth.message ? state.auth.message : null,
currentUsername: state.auth.currentUser ? state.auth.currentUser.username : null,
question: state.auth.currentUser ? state.auth.currentUser.question : null
}
}
export default connect(mapStateToProps)(AnswerForm);
以下是测试:
import React from 'react';
import {AnswerForm} from '../components/answer-form';
import {shallow, mount} from 'enzyme';
describe('<AnswerForm />', () => {
it('changes submitted state', () => {
const spy = jest.fn();
const wrapper = mount(<AnswerForm dispatch={spy}/> );
wrapper.instance();
expect(wrapper.state('submitted')).toEqual(false);
const button = wrapper.find('#button-answer');
button.simulate('click')
expect(wrapper.state('submitted')).toEqual(true);
});
});
当我尝试运行此测试时出现此错误:
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
at Object.it (src/tests/answer-form.test.js:24:44)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
有什么想法吗?除了if语句之外,这是一个非常直接的镜头。不知道这里发生了什么。
答案 0 :(得分:4)
这里的问题是,在模拟过程中,酶或React不会在提交按钮和表单元素之间发生内在的DOM事件传播。
React中的事件系统都是合成的,以便规范化浏览器怪癖,它们实际上都被添加到document
(不是你添加处理程序的节点),并且假事件通过React通过组件冒泡(我强烈建议您在事件系统中深入了解this webinar from the React core team
这使得测试它们有点不直观且有时会出现问题,因为模拟不会触发真正的DOM事件传播
在酶中,在浅层渲染上触发的事件根本不是真实事件,并且不会具有关联的DOM目标。即使使用支持它的DOM片段的mount
,它仍然使用React的合成事件系统,因此simulate
仍然只测试通过组件冒泡的合成事件,它们不会通过真正的DOM,因此模拟单击提交按钮本身不会在表单本身触发submit
DOM事件,因为它的浏览器不是React负责的。 https://github.com/airbnb/enzyme/issues/308
因此,在测试中解决这个问题的两种方法是......
1)从UI测试角度来看,绕过按钮并不理想,但是对于单元测试来说是不干净的,特别是因为它应该与shallow
渲染一起使用以隔离组件。
describe('<AnswerForm />', () => {
const spy = jest.fn();
const wrapper = shallow(<AnswerForm dispatch={spy}/> );
it('should show form initially', () => {
expect(wrapper.find('form').length).toEqual(0);
})
describe('when the form is submitted', () => {
before(() => wrapper.find('form').simulate('submit')))
it('should have dispatched the answer', () => {
expect(spy).toHaveBeenCalled();
});
it('should not show the form', () => {
expect(wrapper.find('form').length).toEqual(0);
});
it('should show the "next" button', () => {
expect(wrapper.find('#button-next').length).toEqual(1);
});
});
});
2)在DOM按钮元素本身上触发一个真实的点击事件,而不是在你的组件上模拟它,好像它是一个Selenium功能测试(这里感觉有点脏),浏览器将传播到之前提交的表单中React捕获submit事件并接管合成事件。因此,这仅适用于mount
describe('<AnswerForm />', () => {
const spy = jest.fn();
const wrapper = mount(<AnswerForm dispatch={spy}/> );
it('should show form initially', () => {
expect(wrapper.find('form').length).toEqual(0);
})
describe('when form is submitted by clicking submit button', () => {
before(() => wrapper.find('#button-answer').getDOMNode().click())
it('should have dispatched the answer', () => {
expect(spy).toHaveBeenCalled();
});
it('should not show the form', () => {
expect(wrapper.find('form').length).toEqual(0);
});
it('should show the "next" button', () => {
expect(wrapper.find('#button-next').length).toEqual(1);
});
});
});
你还要注意我没有测试状态本身。将状态直接作为其纯粹的实现细节进行测试通常是不好的做法(状态改变最终应该导致可以替代地被测试的组件发生更有形的事情)。
在这里,我测试了你的事件导致调度spy被调用了正确的args,现在显示了Next按钮而不是表单。这样,如果您重构内部结构,它会更专注于结果并且不那么脆弱。
答案 1 :(得分:0)
请注意,您正在测试的组件不是AnswerForm class
组件,而是通过将AnswerForm
传递给react-redux
的{{1}}高阶组件而创建的包装组件
如果您使用connect
呈现而非完整shallow
,则可以使用Enzyme API的dive()
function来访问实际的mount
组件。试试这个:
class
另一种选择是直接测试非包装组件实例。为此,您只需更改import React from 'react';
import {AnswerForm} from '../components/answer-form';
import {shallow, mount} from 'enzyme';
describe('<AnswerForm />', () => {
it('changes submitted state', () => {
const spy = jest.fn();
const wrapper = shallow(<AnswerForm dispatch={spy}/> );
expect(wrapper.dive().state('submitted')).toEqual(false);
const button = wrapper.dive().find('#button-answer');
button.simulate('click')
expect(wrapper.dive().state('submitted')).toEqual(true);
});
});
和export
即可。在import
:
answer-form.js
除了包装组件之外,这个export class AnswerForm extends React.Component
...your code
export default connect(mapStateToProps)(AnswerForm);
是非包装组件。然后是export
中的import
:
answer-form.test.js
这样,您可以独立测试import WrappedAnswerForm, { AnswerForm } from 'path/to/answer-form.js`;
功能,假设您不需要测试任何收到的Redux AnswerForm
。查看this GitHub issue以获取更多指导。