I'm trying to test for a dispatched action using chai-spies. I've tried various trouble shooting techniques, but have been unable to get the test to pass.

I've attempted to spy on both the (component.prototype, 'method) and (wrapper.instance().props.store, 'dispatch'), but have been unsuccessful.

Here's the component code:

export class Appointments extends React.Component {
    componentDidMount() {
        this.props.dispatch(fetchAppointments(this.props.user.id));
    }

render() {
    if (this.props.appointments) {
        const list = this.props.appointments.map(l => {
            let appointmentDate = new Date(l.date);

                let day = appointmentDate.getDate();
                if (day < 10) {
                    day = `0${day}`
                }
                let month = appointmentDate.getMonth() + 1;
                if (month < 10) {
                    month = `0${month}`;
                }
                const year = appointmentDate.getFullYear();

                let formattedAppointmentDate = `${month}/${day}/${year}`;
            return {
                id: l._id,
                date: formattedAppointmentDate
            };
        });
        return (
           <div className={"create-appointment-div desktop-hide-2 " + (this.props.isAppointmentInfoShowing ? 'hidden-1' : '')}>
                        <span className={"create-appointment-span " + (this.props.isCreateAppointmentFormShowing ? 'hidden-1' : '')}>
                            Need to create an appointment?
                        </span>
                        <button
                            className={"create-appointment-button "  + (this.props.isCreateAppointmentFormShowing ? 'hidden-1' : '')}
                            onClick={() => this.props.dispatch(chooseCreateAppointment())}
                        >
                            Click here
                        </button>
                    </div>
                    <div className="create-appointment-div mobile-hide">
                        <span className={"create-appointment-span " + (this.props.isCreateAppointmentFormShowing ? 'hidden-1' : '')}>
                            Need to create an appointment?
                        </span>
                        <button
                            className={"create-appointment-button "  + (this.props.isCreateAppointmentFormShowing ? 'hidden-1' : '')}
                            onClick={() => this.props.dispatch(chooseCreateAppointment())}
                        >
                            Click here
                        </button>
                    </div>
                    <div className={"create-appointment-form-component-div " + (this.props.isCreateAppointmentFormShowing ? '' : 'hidden-1')}>
                        <CreateAppointmentForm />
                    </div> 
                </main>
                <Footer />
            </div>
        );
    }
}
}

const mapStateToProps = state => ({
    appointments: state.app.appointments,
    user: state.auth.currentUser,
    isCreateAppointmentFormShowing: state.app.isCreateAppointmentFormShowing,
    isAppointmentInfoShowing: state.app.isAppointmentInfoShowing,
    deletedAppointment: state.app.deletedAppointment,
    isMessageShowing: state.app.isMessageShowing,
    isLoading: state.app.isLoading
});

export default requiresLogin()(connect(mapStateToProps)(Appointments));

Here's the test code:

import React from 'react';
import {shallow, mount} from 'enzyme';
import configureStore from 'redux-mock-store';
import {Provider} from 'react-redux';
import {MemoryRouter} from 'react-router';
import thunk from 'redux-thunk';
import chai, {expect} from 'chai';
import spies from 'chai-spies';

import Appointments from './appointments';
import NavigationBar from './navBar';
import Footer from './footer';
import AppointmentsList from './appointmentsList';
import AppointmentsShow from './appointmentsShow';
import CreateAppointmentForm from './createAppointmentForm';

const middlewares = [thunk];

const mockStore = configureStore(middlewares);

chai.use(spies);

const appointments = [
    {
      address: {
        street: '40 South Greenlake Ave',
        city: 'Jacksonville',
        state: 'FL',
        zipCode: 35421
      },
      _id: '5cb694034859fh37701f316e',
      description: 'Access evaluation',
      date: '2019-01-04T05:00:00.000Z',
      time: '2:15 p.m.',
      with: 'Jason Strickland',
      title: 'MD',
      where: 'Vascular Access Center',
      phoneNumber: '904-943-2942',
      patient: '5cb694034859f123701f3159'
    },
    {
      address: {
        street: '632 Oak St',
        city: 'Jacksonville',
        state: 'FL',
        zipCode: 34423
      },
      _id: '5cb694034859f123701f316d',
      description: 'Back pain',
      date: '2019-01-19T05:00:00.000Z',
      time: '12:30 p.m.',
      with: 'Jessica Brown',
      title: 'Primary care physician',
      where: 'Baptist Primary Care',
      phoneNumber: '904-233-1114',
      patient: '5cb694034859f123701f3159'
    },
    {
      address: {
        street: '402 South Lakeside Dr',
        city: 'Jacksonville',
        state: 'FL',
        zipCode: 35422
      },
      _id: '5cb694034859f123701f316f',
      description: 'Vein mapping',
      date: '2019-02-04T05:00:00.000Z',
      time: '2:10 p.m.',
      with: 'Jason Strickland',
      title: 'MD',
      where: 'Vascular Access Center',
      phoneNumber: '904-943-2942',
      patient: '5cb694034859f123701f3159'
    }
  ];

describe('<Appointments />', () => {
    let wrapper;
    let store;
    let initialState;
    beforeEach(() => {
        initialState = {
            app: {
                selectedAppointments: [],
                selectedLabResult: null,
                isSidebarShowing: false,
                labResults: [],
                isLabResultsInfoShowing: false,
                profile: [],
                loadedBasicProfileInfoFormData: {},
                isUserInfoShowing: false,
                section: 0,
                appointments: [],
                isAppointmentInfoShowing: false,
                areSublinksShowing: false,
                currentDoctor: 0,
                isCreateAppointmentFormShowing: false,
                isCreateDoctorFormShowing: false,
                isEditBasicProfileInfoFormShowing: false,
                selectedAppointmentToEdit: null,
                selectedDoctorToEdit: null,
                loadedAppointmentFormData: {},
                isDoctorMenuShowing: false,
                loadedDoctorFormData: {},
                doctors: [],
                areAppointmentsShowing: false,
                deletedAppointment: null,
                deletedDoctor: null,
                isLoading: true,
                animation: false,
                isEditAppointmentFormShowing: false,
                isEditDoctorFormShowing: false
            },
            auth: {
                loading: false,
                currentUser: {
                    _id: 1
                },
                error: null
            }
        };
        store = mockStore(initialState);
    });

    it('Should render without crashing', () => {
        shallow(<Appointments />);
    });

    it('Should simulate a click event when the button that renders the createAppointmentForm component is clicked', () => {
        initialState.app.appointments = appointments;
        initialState.app.isLoading = false;
        wrapper = mount(
            <Provider store={store}>
                <MemoryRouter initalEntries={['/appointments']}>
                    <Appointments />
                </MemoryRouter>
            </Provider>
        );
        console.log('props', wrapper.instance().props);
        const spy = chai.spy.on(wrapper.instance().props.store, 'dispatch');
        expect(wrapper.find('button.create-appointment-button').at(0).length).to.equal(1);
        wrapper.find('button.create-appointment-button').at(0).simulate('click');
        expect(spy).to.have.been.called();
    });
});

I want the spy to be called but I keep receiving this error:

● <Appointments /> › Should simulate a click event when the button that renders the createAppointmentForm component is clicked

AssertionError: expected { Spy 'object.dispatch' }
function (action) {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }

        return next(action);
      } to have been called

  300 |         expect(wrapper.find('button.create-appointment-button').at(0).length).to.equal(1);
  301 |         wrapper.find('button.create-appointment-button').at(0).simulate('click');
> 302 |         expect(spy).to.have.been.called();
      |                                  ^
  303 |     });
  304 | });
  305 | 

  at Object.called (src/components/appointments.test.js:302:34)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 11 passed, 12 total
Snapshots:   0 total
Time:        6.625s
Ran all test suites matching /appointments.test.js/i.

Any help would be greatly appreciated.

2 Answers

0
Anil Kumar On

Try this out

 // chooseCreateAppointmentFile - file from where chooseCreateAppointment is coming or 
 just spy on chooseCreateAppointment
 const spy = chai.spy.on(chooseCreateAppointmentFile, 'chooseCreateAppointment');
 expect(wrapper.find('button.create-appointment-button').at(0).length).to.equal(1);
 wrapper.update();
 wrapper.find('button.create-appointment-button').at(0).simulate('click');

In Unit testing you should isolate your component and then run assertions on provided inputs. You can just test if chooseCreateAppointment is called or not. No need to check for dispatch.

0
JoshNismo On

Here's the updated code:

const spy = chai.spy.on('actions/index.js', 'chooseCreateAppointment');
        expect(wrapper.find('button.create-appointment-button').at(0).length).to.equal(1);
        wrapper.update();
        wrapper.find('button.create-appointment-button').at(0).simulate('click');
        // const spy = chai.spy.on(wrapper.instance().props.store, 'dispatch');
        // expect(wrapper.find('button.create-appointment-button').at(0).length).to.equal(1);
        // wrapper.find('button.create-appointment-button').at(0).simulate('click');
        expect(spy).to.have.been.called();

Now I'm getting this error:

TypeError: Cannot use 'in' operator to search for 'chooseCreateAppointment' in actions/index.js

  296 |         );
  297 |         console.log('props', wrapper.instance().props);
> 298 |         const spy = chai.spy.on('actions/index.js', 'chooseCreateAppointment');
      |                              ^
  299 |         expect(wrapper.find('button.create-appointment-button').at(0).length).to.equal(1);
  300 |         wrapper.update();
  301 |         wrapper.find('button.create-appointment-button').at(0).simulate('click');

  at Sandbox.on (node_modules/chai-spies/lib/spy.js:65:20)
  at Function.chai.spy.on (node_modules/chai-spies/lib/spy.js:266:31)
  at Object.on (src/components/appointments.test.js:298:30)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 11 passed, 12 total
Snapshots:   0 total
Time:        7.364s
Ran all test suites matching /appointments.test.js/i.