3
votes

I am doing Unit tests with jest and enzyme. I have following connected component with hooks. I called redux actions to load data.

import React, {useEffect, useState, useCallBack} from "react";
import {connect} from "react-redux";
import CustomComponent  from "../Folder";
import { loadData, createData, updateData } from "../../redux/actions";

const AccountComponent = (props) => {
  const total = 50;
  const [aIndex, setAIndex] = useState(1);
  const [arr, setArr] = useState(['ds,dsf']);
 //... some state variables here

const getData = () => {
   props.loadData(aIndex, total, arr);
}

useEffect(() => {

  getData();

},[aIndex, total])

//some other useEffect and useCallback

return(
 <React.Fragment>
   <CustomComponent {...someParam}/>
    <div>
     ...
    </div>
 </React.Fragment>
)

}

const mapStateToProps = (state) => {
 const { param1, param2, parma3 } = state.AccountData;
 return {
  param1,
  param2,
  parma3
 }
}

export default connect(mapStateToProps, { loadData, createData, updateData })(AccountComponent)

Here, like following I created some test case for above component.

import AccountComponent from "../";
import React from "react";
import renderer from "react-test-renderer"

describe("AccountComponent component", () => {

 const loadData = jest.fn();
 let wrapper;

 it("snapshot testing", () => {
  const tree = renderer.create(<AccountComponent loadData={loadData} />).toJSON();
  expect(tree).toMatchSnapshot();
 })

  beforeEach(() => {
    wrapper = shallow(<AccountComponent loadData={loadData} />).instance();
  });

  it('should call loadData', () => {
    expect(wrapper.loadData).toHaveBeenCalled();
  });
})

But, It doesn't pass and shows error.

Error for snapshot testing:

invariant violation element type is invalid: expected string or a class/function

Error for method call testing:

Cannot read property 'loadData' of undefined.

Enzyme Internal error: Enzyme expects and adapter to be configured, but found none. ...

Not sure what the issue as I am not good in unit testing.

I am using react-redux 7.

Any help would be greatly appreciated.

Edit:

I also tried with provider like following. But, didn't help.

import { Provider } from "react-redux";
import {createStore} from "redux";
import reducer from "../../reducers";

const store = createStore(reducer);

it("snapshot testing", () => {
      const tree = renderer.create(<Provider store={store}><AccountComponent loadData={loadData} /></Provider>).toJSON();
      expect(tree).toMatchSnapshot();
     })


beforeEach(() => {
        wrapper = shallow(<Provider store={store}><AccountComponent loadData={loadData} /></Provider>).instance();
      });
3
I think, you are testing implementation details by checking if loadData was called or not. You should test if data is visible at screen to end user or not (without worrying how it is loaded or which function is called to load it). And, it seems like you are testing a component connected with Redux store, but you provided it no Store when testing it. See redux.js.org/recipes/writing-tests - Ajeet Shah
try mocking the actions file content for loadData and as mentioned by Ajeet you need to provide provider with store as you component is connected to redux-store - Chandan
@AjeetShah Please check my edited question. I tried with store but, still not working with error. And Actually, in this page functionality is like to load data and display to datagrid. So, That's why want to test if loadData function called properly or not. And all data stored to state. there can be possible some time there will be no data. - ketan
@Chandan. Please check my edited question. I tried with store but, still not working with error. - ketan
You're exporting/imported the connected version of AccountComponent so that component does not have a prop loadData. That comes from Redux. @AjeetShah is correct that you need to load it inside a <Provider>. You could potentially export the unconnected version of the component to test and pass your loadData function as a prop which should work, but that test is not as informative. - Linda Paiste

3 Answers

2
votes

In your case when you are using connected components in the same file you need to pass the state through Provider. Also, you need to configure your enzyme. And finally, when you are using react hooks, you will need to do asynchronous unit tests, because effects are async. When you are trying to check if any function has been called you need to "spy" on it.

import configureStore from 'redux-mock-store';
import React from 'react';
import renderer from 'react-test-renderer';
import Enzyme, { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import Adapter from 'enzyme-adapter-react-16';
import { act } from 'react-dom/test-utils';
import createSagaMiddleware from 'redux-saga';
import AccountComponent from '../AccountComponent';
import * as actions from '../../../redux/actions';

jest.mock('../../../redux/actions', () => ({
  loadData: jest.fn(),
  createData: jest.fn(),
  updateData: jest.fn(),
}));

const loadData = jest.spyOn(actions, 'loadData');

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

const configureMockStore = configureStore([createSagaMiddleware]);

const initialState = {
  AccountData: {
    param1: 'param1',
    param2: 'param2',
    parma3: 'parma3 ',
  },
};

const store = configureMockStore(initialState);

describe('AccountComponent component', () => {
  let wrapper;

  it('snapshot testing', () => {
    const tree = renderer
      .create(
        <Provider store={store}>
          <AccountComponent />
        </Provider>,
      )
      .toJSON();
    expect(tree).toMatchSnapshot();
  });

  beforeEach(async () => {
    await act(async () => {
      wrapper = shallow(
        <Provider store={store}>
          <AccountComponent />
        </Provider>,
      );
    });

    await act(async () => {
      wrapper.update();
    });
  });

  it('should call loadData', () => {
    expect(loadData).toHaveBeenCalled();
  });
});

Please mock your AccountData state with properties which will be used in that component. Also, I am not sure where is your test file is located, so you might need to change import path from '../../../redux/actions' to you actions file path. Finally, I am not sure what middleware you are using, so fill free to replace import createSagaMiddleware from 'redux-saga'; with your middleware for redux.

0
votes

If you are using react it already comes with @testing-library and you don't need enzyme to do snapshot testing. This is how I do my snapshot testing.

  import React, { Suspense } from "react";
  import { screen } from "@testing-library/react";
  import "@testing-library/jest-dom/extend-expect";

  import AccountComponent from "../";

  import store from "./store";// you can mock the store if your prefer too

  describe("<AccountComponent />", () => {
    test("it should match snapshot", async () => {
      expect.assertions(1);
      const { asFragment } = await render(
        <Suspense fallback="Test Loading ...">
          <Provider store={store}>
            <AccountComponent />
          </Provider>
        </Suspense>
      );
      expect(asFragment()).toMatchSnapshot();
    });
  });
0
votes

When it is a functional component and you are using hooks, unit tests may not work with shallow rendering. You have to use 'renderHooks' instead to create a wrapper. Please refer https://react-hooks-testing-library.com/ for more details.