Reputation: 1688
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.
Upvotes: 44
Views: 202816
Reputation: 2402
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
Upvotes: 6
Reputation: 1
My goal was to set initial values for useState. but the solutions I found seemed odd* to me, and when I tried to apply them, I was unable to set the state after mocking it. So I decided to use the initial optional props of the component instead.
export default function Chat({ initialLoading = false, initialDataSource = []}:ChatProps) {
const [loading, setLoading] = useState<boolean>(initialLoading);
const [dataSource, setDataSource] = useState<TableDataType[]>(initialDataSource);
it('shows a table correctly', () => {
const mockData = mockDataSource;
const firstSupplier = mockData[0].supplier_company;
render(<Chat initialDataSource={mockData} />);
expect(screen.getByText(firstSupplier)).toBeInTheDocument();
});
*What if I change the order of the code? Do I have to reorganize for the test again?
my answer does not respond the question but if someone is facing a same problem as me you can use this as an alternative approach.
Upvotes: 0
Reputation: 147
heres how you can do it easy without Enzyme. And you can even do this if you useContext.
MyComponent.js
const [comments, setComments] = useState();
MyComponent.test.js
const comments = [{id:1, title: "first comment", body: "bla bla"}]
jest.spyOn(React, 'useState').mockReturnValueOnce([comments, jest.fn()]);
const { debug } = render(<MyComponent />);
debug();
Last two lines of code is to kinda see how the DOM would look like, to see if how your comments state looks like when rendered.
Upvotes: 0
Reputation: 4323
I used for multiple useState()
Jest mocks the following setup in the component file
const [isLoading, setLoading] = React.useState(false);
const [isError, setError] = React.useState(false);
Please note the useState
mock will just work with React.useState()
derivation.
..and in the test.js
describe('User interactions at error state changes', () => {
const setStateMock = jest.fn();
beforeEach(() => {
const useStateMock = (useState) => [useState, setStateMock];
React.useState.mockImplementation(useStateMock)
jest.spyOn(React, 'useState')
.mockImplementationOnce(() => [false, () => null]) // this is first useState in the component
.mockImplementationOnce(() => [true, () => null]) // this is second useState in the component
});
it('Verify on list the state error is visible', async () => {
render(<TodoList />);
....
Upvotes: 1
Reputation: 11
NOT CHANGING TO React.useState
This approach worked for me:
//import useState with alias just to know is a mock
import React, { useState as useStateMock } from 'react'
//preseve react as it actually is but useState
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}))
describe('SearchBar', () => {
const realUseState: any = useStateMock //create a ref copy (just for TS so it prevents errors)
const setState = jest.fn() //this is optional, you can place jest.fn directly
beforeEach(() => {
realUseState.mockImplementation((init) => [init, setState]) //important, let u change the value of useState hook
})
it('it should execute setGuestPickerFocused with true given that dates are entered', async () => {
jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => ['', () => null]) //place the values in the order of your useStates
.mockImplementationOnce(() => ['20220821', () => null]) //...
.mockImplementationOnce(() => ['20220827', () => null]) //...
jest.spyOn(uiState, 'setGuestPickerFocused').mockReturnValue('')
getRenderedComponent()
expect(uiState.setGuestPickerFocused).toHaveBeenCalledWith(true)
})
})
My component
const MyComp: React.FC<MyCompProps> = ({
a,
b,
c,
}) => {
const [searchQuery, setSearchQuery] = useState('') // my first value
const [startDate, setStartDate] = useState('') // my second value
const [endDate, setEndDate] = useState('') // my third value
useEffect(() => {
console.log(searchQuery, startDate, endDate) // just to verifiy
}, [])
Hope this helps!
Upvotes: 0
Reputation: 189
SOLUTION WITH DE-STRUCTURING
You don't need to use React.useState
- you can still destructure in your component.
But you need to write your tests in accordance to the order in which your useState calls are made. For example, if you want to mock two useState calls, make sure they're the first two useState calls in your component.
In your component:
import React, { useState } from 'react';
const [firstOne, setFirstOne] = useState('');
const [secondOne, setSecondOne] = useState('');
In your test:
import React from 'react';
jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => [firstInitialState, () => null])
.mockImplementationOnce(() => [secondInitialState, () => null])
.mockImplementation((x) => [x, () => null]); // ensures that the rest are unaffected
Upvotes: 14
Reputation: 76
I have spent a lot of time but found good solution for testing multiple useState in my app.
export const setHookTestState = (newState: any) => {
const setStateMockFn = () => {};
return Object.keys(newState).reduce((acc, val) => {
acc = acc?.mockImplementationOnce(() => [newState[val], setStateMockFn]);
return acc;
}, jest.fn());
};
where newState is object with state fields in my component;
for example:
React.useState = setHookTestState({
dataFilter: { startDate: '', endDate: '', today: true },
usersStatisticData: [],
});
Upvotes: 1
Reputation: 1390
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.
Upvotes: 30
Reputation: 489
//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);
});
Upvotes: 7
Reputation: 315
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.
Upvotes: 13
Reputation: 2831
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
Upvotes: 36