How to set initial state for useState Hook in jest and enzyme?
Currently Im using functional component with react hooks. But I'm unable to test the useState
hook completely. Consider a scenario like, in useEffect
hook I'm doing an API call and setting value in the useState
. For jest/enzyme I have mocked data to test but I'm unable to set initial state value for useState
in jest.
const [state, setState] = useState([]);
I want to set initial state as array of object in jest. I could not find any setState function as similar like class component.
You can mock React.useState
to return a different initial state in your tests:
// Cache original functionality
const realUseState = React.useState
// Stub the initial state
const stubInitialState = ['stub data']
// Mock useState before rendering your component
jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => realUseState(stubInitialState))
Reference: https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga
First, you cannot use destructuring in your component. For example, you cannot use:
import React, { useState } from 'react';
const [myState, setMyState] = useState();
Instead, you have to use:
import React from 'react'
const [myState, setMyState] = React.useState();
Then in your test.js
file:
test('useState mock', () => {
const myInitialState = 'My Initial State'
React.useState = jest.fn().mockReturnValue([myInitialState, {}])
const wrapper = shallow(<MyComponent />)
// initial state is set and you can now test your component
}
If you use useState hook multiple times in your component:
// in MyComponent.js
import React from 'react'
const [myFirstState, setMyFirstState] = React.useState();
const [mySecondState, setMySecondState] = React.useState();
// in MyComponent.test.js
test('useState mock', () => {
const initialStateForFirstUseStateCall = 'My First Initial State'
const initialStateForSecondUseStateCall = 'My Second Initial State'
React.useState = jest.fn()
.mockReturnValueOnce([initialStateForFirstUseStateCall, {}])
.mockReturnValueOnce([initialStateForSecondUseStateCall, {}])
const wrapper = shallow(<MyComponent />)
// initial states are set and you can now test your component
}
// actually testing of many `useEffect` calls sequentially as shown
// above makes your test fragile. I would recommend to use
// `useReducer` instead.
If I recall correctly, you should try to avoid mocking out the built-in hooks like useState
and useEffect
. If it is difficult to trigger the state change using enzyme's invoke()
, then that may be an indicator that your component would benefit from being broken up.
- Below function will return state
const setHookState = (newState) => jest.fn().mockImplementation(() => [ newState, () => {}, ]);
- Add below to use react
const reactMock = require('react');
In your code, you must use React.useState()
to this work, else it won't work
const [arrayValues, setArrayValues] = React.useState();
const [isFetching, setFetching] = React.useState();
- Then in your test add following, mock state values
reactMock.useState = setHookState({ arrayValues: [], isFetching: false, });
Inspiration: Goto
//Component
const MyComponent = ({ someColl, someId }) => {
const [myState, setMyState] = useState(null);
useEffect(() => {loop every time group is set
if (groupId) {
const runEffect = async () => {
const data = someColl.find(s => s.id = someId);
setMyState(data);
};
runEffect();
}
}, [someId, someColl]);
return (<div>{myState.name}</div>);
};
// Test
// Mock
const mockSetState = jest.fn();
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: initial => [initial, mockSetState]
}));
const coll = [{id: 1, name:'Test'}, {id: 2, name:'Test2'}];
it('renders correctly with groupId', () => {
const wrapper = shallow(
<MyComponent comeId={1} someColl={coll} />
);
setTimeout(() => {
expect(wrapper).toMatchSnapshot();
expect(mockSetState).toHaveBeenCalledWith({ id: 1, name: 'Test' });
}, 100);
});