我在React应用程序中有一个相当基本的react组件。我想测试提交表单时状态的“提交”部分从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);
Run Code Online (Sandbox Code Playgroud)
这是测试:
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);
});
});
Run Code Online (Sandbox Code Playgroud)
尝试运行此测试时出现此错误:
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)
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?除了if语句之外,这是一个非常简单的方法。不知道这是怎么回事。
这里的问题是,在模拟过程中,酶或React无法完成预期在提交按钮和表单元素之间发生的固有DOM事件传播。
为了规范浏览器的怪癖,React中的事件系统都是合成的,它们实际上都被添加到了document(而不是您添加了处理程序的节点),并且假事件在React中通过组件冒泡(我强烈建议您从反应核心团队在事件系统中深入解释)
这使得对其进行测试有些不直观,有时还会有问题,因为模拟不会触发真正的DOM事件传播
在酶中,浅渲染上触发的事件根本不是真实事件,并且不会具有关联的DOM目标。即使使用mount具有DOM片段作为后盾的组件,它仍然使用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);
});
});
});
Run Code Online (Sandbox Code Playgroud)
2)在DOM按钮元素本身上触发真正的click事件,而不是像在Selenium功能测试上那样在组件上模拟它(因此在这里感觉有点脏),浏览器将其传播到React捕获之前的表单提交中。提交事件并接管综合事件。因此,这仅适用于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);
});
});
});
Run Code Online (Sandbox Code Playgroud)
您还会注意到我没有测试状态本身。通常直接将状态作为其纯实现细节进行测试是不明智的做法(状态更改最终将导致更实际的事情发生在可以测试的组件上)。
在这里,我改为测试您的事件是否导致使用正确的参数调用了调度间谍,并且现在显示了“下一步”按钮而不是表单。这样,如果您重构内部结构,它将更加专注于结果,而减少了脆弱性。