开玩笑-在表单的handleSubmit中模拟函数调用

时间:2018-11-29 06:59:16

标签: reactjs testing redux jestjs enzyme

我正在尝试编写一个模拟表单handleSubmit中函数调用的测试,但是,我无法显示该函数已被调用。

表格如下:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import signUp from '../../actions/users/sign_up';
import PropTypes from 'prop-types';

class Signup extends Component {
  constructor (props) {
    super(props);

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.showError = this.showError.bind(this);
  }

  handleChange(event) {
    const target = event.target;

    this.setState({ [ target.name ]: target.value });
  }

  handleSubmit(event) {
    event.preventDefault();
    this.props.signUp(this.state);
  }

  showError(type) {
    if (this.state && this.state.error && this.state.error.data.errors[ type ]) {
      return this.state.error.data.errors[ type ][ 0 ];
    }
  }

  componentDidUpdate (prevProps, prevState) {
    const props = this.props;

    if (prevProps === props) {
      return;
    }

    this.setState({
      ...props,
    });
  }

  render () {
    return (
        <div className='container-fluid'>
            <div className='row'>
                <div className='col col-md-6 offset-md-3 col-sm-12 col-12'>
                    <div className='card'>
                        <div className='card-header'>
                            <h4>Sign Up</h4>
                        </div>
                        <div className='card-body'>
                            <form onSubmit={ this.handleSubmit } >
                                <div className="form-row">
                                    <div className="form-group col-md-12">
                                        <label htmlFor="email">Email</label>
                                        <input
                        type="email"
                        name="email"
                        className={ `form-control ${ this.showError('email') ? 'is-invalid' : '' }` }
                        id="email"
                        placeholder="Email"
                        onChange={ this.handleChange }
                      />
                                        <div className="invalid-feedback">
                                            { this.showError('email') }
                                        </div>
                                    </div>
                                </div>
                                <div className="form-row">
                                    <div className="form-group col-md-12">
                                        <label htmlFor="username">Username</label>
                                        <input
                        type="text"
                        name="username"
                        className={ `form-control ${ this.showError('username') ? 'is-invalid' : '' }` }
                        id="username"
                        placeholder="Username"
                        onChange={ this.handleChange }
                      />
                                        <div className="invalid-feedback">
                                            { this.showError('username') }
                                        </div>
                                    </div>
                                </div>
                                <div className="form-row">
                                    <div className="form-group col-md-12">
                                        <label htmlFor="password">Password</label>
                                        <input
                          type="password"
                          name="password"
                          className={ `form-control ${ this.showError('password') ? 'is-invalid' : '' }` }
                          id="password"
                          placeholder="Password"
                          onChange={ this.handleChange }
                        />
                                        <div className="invalid-feedback">
                                            { this.showError('password') }
                                        </div>
                                    </div>
                                    <button type="submit" className="btn btn-primary">Sign Up</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    )
  }
}

function mapStateToProps (state) {
  return {
    email: state.UsersReducer.email,
    username: state.UsersReducer.username,
    password: state.UsersReducer.password,
    error: state.UsersReducer.error,
  }
}

function mapDispatchToProps (dispatch) {
  return bindActionCreators({
    signUp: signUp,
  }, dispatch);
}

Signup.propTypes = {
  email: PropTypes.string,
  username: PropTypes.string,
  password: PropTypes.string,
  signUp: PropTypes.func.isRequired
}

export default connect(mapStateToProps, mapDispatchToProps)(Signup);

signUp操作如下:

import { SIGN_UP, SHOW_USER_ERRORS } from '../types';
import axios from 'axios';
import { API_ROOT,  setLocalStorageHeader } from './../../api-config';
import { push } from 'react-router-redux';

export default function signUp (params) {
  return dispatch => {
    axios.post(`${ API_ROOT }/auth.json`, params).then(res => {
      setLocalStorageHeader(res);
      dispatch(push('/profile'));
      dispatch(signUpAsync(res.data));
    }).catch(error => {
      dispatch({ type: SHOW_USER_ERRORS, payload: { error: error.response } });
    });
  }
}

function signUpAsync (data) {
  return {
    type: SIGN_UP,
    payload: data
  };
}

我正在尝试模拟以下事实:将使用表单输入(表单状态(emailusernamepassword)中获得的值提交表单

我目前的测试是:

import React from 'react';
import { shallow, mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { bindActionCreators } from 'redux';
import thunk from 'redux-thunk';

import Signup from '../../../components/users/signup';
import UsersReducer from '../../../reducers/reducer_users';

describe('<Signup />', () => {
  describe('render()', () => {
    test('submits the form data',  async () => {
      const mockStore = configureStore([thunk]);

      const initialState = {
        UsersReducer: {
          email: '',
          username: '',
          password: '',
        },
      };

      const store = mockStore(initialState);
      const dispatchMock = jest.spyOn(store, 'dispatch');

      const signUp = jest.fn();

      const wrapper = shallow(<Signup store={store} signUp={signUp} />);
      const component = wrapper.dive();

      component.find('#email').simulate(
        'change', {
          target: {
            name: 'email', value: 'foo@gmail.com'
          }
        }
      );

      component.find('#email').simulate(
        'change', {
          target: {
            name: 'username', value: 'foo'
          }
        }
      );

      component.find('#password').simulate(
        'change', {
          target: {
            name: 'password',
            value: '1234567',
          }
        }
      )

      component.find('form').simulate(
        'submit', {
          preventDefault() {}
        }
      )

      expect(dispatchMock).toHaveBeenCalled();

      expect(signUp).toHaveBeenCalledWith({
        email: 'foo@gmail.com',
        username: 'foo',
        password: '12345678'
      });
    });
  });
});

但是无论尝试如何,我都会不断收到以下错误消息。

Expected mock function to have been called with:
  [{"email": "foo@gmail.com", "password": "12345678", "username": "foo"}]
But it was not called.

我认为这是由于signUpshallow(<Signup store={store} signUp={signUp} />)中没有得到正确的模拟,因为当我这样做时,console.log(wrapper.props())会得到:

{
...
signUp: [Function],
...
}

不是表明它是一个模拟函数:

{ [Function: mockConstructor]
   _isMockFunction: true,
...
}

我知道测试的signUp正在调用dispatch动作。当我向其中添加params时,在signUp动作中也可以看到console.log(params)

任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

在向视图添加redux时,在signUp中添加mapDispatchToProps

使用redux-mock-store时,您可以访问store.getActions()所调用的所有操作,因此,在您的情况下,无需传递signUp作为间谍,该间谍将被{{1}覆盖},看起来可能像这样:

mapDispatchToProps

答案 1 :(得分:0)

因此,经过 次反复试验后,解决方案是模拟动作调用本身,方法是添加import * as signUp from '../../../actions/users/sign_up';并对其进行模拟const signUpActionMock = jest.spyOn(signUp, 'default');

现在测试如下:

import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';

import Signup from '../../../components/users/signup';
import UsersReducer from '../../../reducers/reducer_users';

// Turns out this import allowed the signUp action to be mocked
import * as signUp from '../../../actions/users/sign_up';

describe('<Signup />', () => {
  describe('render()', () => {
    test('submits the form data', () => {
      const middlewares = [thunk]

      // Mock the signUp action call
      const signUpActionMock = jest.spyOn(signUp, 'default');

      const mockStore = configureStore(middlewares);

      const initialState = {
        UsersReducer: {
          email: '',
          username: '',
          password: '',
        },
      };

      const store = mockStore(initialState);

      const wrapper = shallow(<Signup store={store} />);
      const component = wrapper.dive();

      component.find('#email').simulate(
        'change', {
          target: {
            name: 'email', value: 'foo@gmail.com'
          }
        }
      );

      component.find('#email').simulate(
        'change', {
          target: {
            name: 'username', value: 'foo'
          }
        }
      );

      component.find('#password').simulate(
        'change', {
          target: {
            name: 'password',
            value: '12345678',
          }
        }
      );

      component.find('form').simulate(
        'submit', {
          preventDefault() {}
        }
      );

      expect(signUpActionMock).toHaveBeenCalledWith({
        email: 'foo@gmail.com',
        username: 'foo',
        password: '12345678'
      });
    });
  });
});