目前我使用带有反应钩子的功能组件。但是我无法完全测试useState
钩子。考虑一个场景,例如,useEffect
钩子中,我正在执行 API 调用并在useState
中设置值。对于开玩笑/酶,我已经模拟了要测试的数据,但我无法在开玩笑中为useState
设置初始状态值。
const [state, setState] = useState([]);
我想在开玩笑中将初始状态设置为对象数组。我找不到任何类似于类组件的 setState 函数。
您可以模拟React.useState
以在测试中返回不同的初始状态:
// 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))
参考: https://dev.to/theactualgivens/testing-react-hook-state-changes-2oga
首先,不能在组件中使用解构。例如,您不能使用:
import React, { useState } from 'react';
const [myState, setMyState] = useState();
相反,您必须使用:
import React from 'react'
const [myState, setMyState] = React.useState();
然后在test.js
文件中:
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
}
如果在组件中多次使用 useState 挂钩:
// 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.
没记错的话,你应该尽量避免嘲笑像useState
和useEffect
这样的内置钩子。如果很难使用酶的invoke()
触发状态变化,那么这可能表明您的组件将从分解中受益。
解构的解决方案
您不需要使用 React.useState
- 您仍然可以在组件中解构。
但是,您需要根据进行 useState 调用的顺序编写测试。例如,如果要模拟两个 useState 调用,请确保它们是组件中的前两个 useState 调用。
在组件中:
import React, { useState } from 'react';
const [firstOne, setFirstOne] = useState('');
const [secondOne, setSecondOne] = useState('');
在您的测试中:
import React from 'react';
jest
.spyOn(React, 'useState')
.mockImplementationOnce(() => [firstInitialState, () => null])
.mockImplementationOnce(() => [secondInitialState, () => null])
.mockImplementation((x) => [x, () => null]); // ensures that the rest are unaffected
- 下面的函数将返回状态
const setHookState = (newState) =>
jest.fn().mockImplementation(() => [
newState,
() => {},
]);
在下面添加以使用反应
const reactMock = require('react');
在你的代码中,你必须使用React.useState()
来完成这项工作,否则它不起作用
const [arrayValues, setArrayValues] = React.useState();`
const [isFetching, setFetching] = React.useState();
然后在测试中添加以下模拟状态值
reactMock.useState = setHookState({
arrayValues: [],
isFetching: false,
});
灵感:转到
//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);
});
我花了很多时间,但找到了在我的应用程序中测试多个 useState 的好解决方案。
export const setHookTestState = (newState: any) => {
const setStateMockFn = () => {};
return Object.keys(newState).reduce((acc, val) => {
acc = acc?.mockImplementationOnce(() => [newState[val], setStateMockFn]);
return acc;
}, jest.fn());
};
其中 newState 是我的组件中带有状态字段的对象;
例如:
React.useState = setHookTestState({
dataFilter: { startDate: '', endDate: '', today: true },
usersStatisticData: [],
});
我用于多个useState()
开玩笑在组件文件中模拟以下设置
const [isLoading, setLoading] = React.useState(false);
const [isError, setError] = React.useState(false);
请注意,useState
模拟将只适用于React.useState()
派生。
..并在测试中.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 />);
....
这里有如何在没有酶的情况下轻松做到这一点。如果你使用上下文,你甚至可以做到这一点。
我的组件.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();
最后两行代码是看 DOM 的样子,看看你的注释状态在呈现时是否是什么样子的。
不会更改为
React.useState
这种方法对我有用:
//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)
})
})
我的组件
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
}, [])
希望这有帮助!