reactjstestingreduxredux-mock-store

Testing React Redux - cannot read properties of undefined, or wrapper undefined


Im having a bit of a problem setting up a Redux store in my component for my test suite. The problem is that even if I try a unconnected mount then the test throws errors looking for variables in authState. I have the following component:

import React, { Component } from 'react';
import { Link, Redirect } from "react-router-dom";
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import * as authActions from '../../../actions/auth.actions';

export class PasswordReset extends Component {
    constructor(props) {
        super(props);
        this.state = {
            email: '',
            emailConfirm: '',
            message: ""
        };
    }

    handleChange = e => {
        e.preventDefault();
        const { value, name } = e.target;

        if (name === 'email') {
            this.setState({...this.state, email: value.trim()});
        } else if (name === 'secondary') {
            this.setState({...this.state, emailConfirm: value.trim()});
        }
    }

    handleSubmit = e => {
        e.preventDefault();
        if (this.state.email.length === 0 && this.state.emailConfirm.length === 0) {
            this.setState({message: "Please Enter and Confirm Email Before Submit"});
            return;
        }

        if (this.state.email !== this.state.emailConfirm) {
            this.setState({message: 'Emails Do Not Match, Please Try Again'});
            return;
        }

        this.props.authActions.resetPassword(this.state.email);
    }

    render() {
        if (this.props.authState.resetStatus === 'reset') {
            return <Redirect to='/login' />
        }
        const error = this.props.authState.resetError === 'TypeError: Failed to fetch' ? 'Agent Id does not exist' : false
        return (
            <div className={'container-login100'}>
                <div className={'wrap-login100 p-t-85 p-b-20'}>
                    <form className={'login100-form validate-form'}>
                        <span className={'login100-form-title p-b-70'}>Reset Password</span>
                        <div className={'wrap-input100 validate-input m-t-85 m-b-35'}>
                        <div style={{margin: '0 auto', alignItems: 'center', justifyContent: 'center', display: 'flex', color: 'red'}}>
                        {error || this.state.message}
                        </div>
                            <input
                                onChange={e => this.handleChange(e)}
                                className={'input100'}
                                name='email'
                                value={this.state.email}
                                placeholder="Please Enter Your Email"
                                required
                            />
                        </div>
                        <div className={'wrap-input100 validate-input m-t-85 m-b-35'}>
                            <input
                                onChange={e => this.handleChange(e)}
                                className={'input100'}
                                name="secondary"
                                value={this.state.secondary}
                                placeholder="Please Confirm By Re-entering Your Email"
                                required
                            />
                        </div>
                        <div className={'container-login100-form-btn'}>
                            <button className={'login100-form-btn btn-disabled'} onClick={e => this.handleSubmit(e)}>Reset Password</button>
                        </div>
                        <div className={'container-login100-form-btn'}>
                            <Link to='/login'>Back to Login</Link>
                        </div>
                    </form>
                </div>
            </div>
        )
    }
}

const mapStateToProps = state => ({
    authState: state.auth,
})

const mapDispatchToProps = dispatch => ({
    authActions: bindActionCreators({resetPassword: authActions.resetPassword}, dispatch),
})

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

And the following test structure:

import React from 'react';
import configureStore from 'redux-mock-store';
import Enzyme, {mount, shallow} from 'enzyme';
import Provider from 'react-redux';
import Adapter from 'enzyme-adapter-react-16';

import ConnectedPasswordReset, {PasswordReset} from '../';

Enzyme.configure({adapter: new Adapter()});

const mockStore = configureStore();
let wrapper, store, mWrapper;
const initialState = {isLoggedIn: false, isInProgress: false, email: null, error: null, resetError: null, resetStatus: null};

describe('PasswordReset component', () => {
    beforeEach(() => {
        store = mockStore(initialState);
        wrapper = shallow(<PasswordReset store={store} />);
        mWrapper = mount(<Provider store={store}><ConnectedPasswordReset /></Provider>);
    })

    it('shallow - should have initial state.email of ""', () => {
        expect(wrapper.state().email).toEqual('');
    });

    it('mount - should have a initial state.email of ""', () => {
        expect(mWrapper.state().email).toEqual('');
    });
});

Im getting the following output:

testing output


Solution

  • Here is the setup that finally worked for me. You can also use mount, but you will need to import your Router and wrap the component in it.

    import React from 'react';
    import Enzyme, { shallow } from 'enzyme';
    import Adapter from 'enzyme-adapter-react-16';
    import { PasswordReset } from '../';
    
    Enzyme.configure({adapter: new Adapter()});
    
    const setup = () => {
        let props = {
            authState: {
               isLoggedIn: false, 
               isInProgress: false, 
               email: null, 
               error: null, 
               resetError: null, 
               resetStatus: null
            },
            authActions: {
                login: jest.fn(),
                resetPassword: jest.fn()
            }
        }
    
        let resetWrapper = shallow(<PasswordReset {...props}/>);
    
        return {props, resetWrapper};
    };
    
    describe('PasswordReset component', () => {
        const {resetWrapper} = setup();
    
        describe('initial render', () => {
            it('should have initial state.email of ""', () => {
                expect(resetWrapper.state('email')).toEqual('');
            });
    
            it('should have a initial internal state.emailConfirm of ""', () => {
                expect(resetWrapper.state('emailConfirm')).toEqual('');
            });
    
            it('should have one form component', () => {
                expect(resetWrapper.find('form').length).toBe(1);
            })
    
            it('should have two input fields', () => {
                expect(resetWrapper.find('input').length).toBe(2);
            });
        });
    });