3

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(); }); 
5
  • 1
    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 Commented Jun 13, 2021 at 12:50
  • 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 Commented Jun 13, 2021 at 17:00
  • @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. Commented Jun 14, 2021 at 12:56
  • @Chandan. Please check my edited question. I tried with store but, still not working with error. Commented Jun 14, 2021 at 12:56
  • 1
    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. Commented Jun 17, 2021 at 1:51

3 Answers 3

3
+50

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.

Sign up to request clarification or add additional context in comments.

5 Comments

I am getting the error. renderer .create(. "InVariant violation Invalid hook call: hooks can only be call Inside of the body..."
Also, the question. What is import { act } from 'react-dom/test-utils'; ?
First error - reactjs.org/warnings/…. Act doc: reactjs.org/docs/test-utils.html#act It is hard to figure out your problem without full code and package.json file
For second. Because it shows error here. expect(loadData).toHaveBeenCalled() Expected mock function to have been called.
Make sure import * as actions from '../../../redux/actions'; and jest.mock('../../../redux/actions', () => ({ have the same action file location
0

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(); }); }); 

2 Comments

What is Suspense here?
Sorry it's being imported from react, I missed that.
0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.