酶测试-找不到按钮

J S*_*olt 2 reactjs enzyme

我在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语句之外,这是一个非常简单的方法。不知道这是怎么回事。

ale*_*ill 5

这里的问题是,在模拟过程中,酶或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)

您还会注意到我没有测试状态本身。通常直接将状态作为其纯实现细节进行测试是不明智的做法(状态更改最终将导致更实际的事情发生在可以测试的组件上)。

在这里,我改为测试您的事件是否导致使用正确的参数调用了调度间谍,并且现在显示了“下一步”按钮而不是表单。这样,如果您重构内部结构,它将更加专注于结果,而减少了脆弱性。