酶测试 - 无法找到按钮

时间:2018-01-10 21:34:11

标签: reactjs enzyme

我在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语句之外,这是一个非常直接的镜头。不知道这里发生了什么。

2 个答案:

答案 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以获取更多指导。