useState
钩子。考虑这样的场景,在
useEffect
钩子中,我正在执行 API 调用并在
useState
中设置值。对于笑话/酶,我已经模拟了要测试的数据,但我无法在笑话中设置
useState
的初始状态值。
const [state, setState] = useState([]);
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
//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);
});
const setHookState = (newState) =>
jest.fn().mockImplementation(() => [
newState,
() => {},
]);
添加以下内容以使用react
const reactMock = require('react');
在您的代码中,您必须使用 React.useState()
来完成此工作,否则它将无法工作
const [arrayValues, setArrayValues] = React.useState();`
const [isFetching, setFetching] = React.useState();
然后在您的测试中添加以下模拟状态值
reactMock.useState = setHookState({
arrayValues: [],
isFetching: false,
});
灵感:
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()
Jest 在组件文件中模拟了以下设置
const [isLoading, setLoading] = React.useState(false);
const [isError, setError] = React.useState(false);
请注意 useState
模拟仅适用于
React.useState()
推导。..在 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 />);
....
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();
最后两行代码是为了看看 DOM 是什么样子,看看渲染时你的评论状态是否是什么样子。
useState 设置初始值。但我发现的解决方案对我来说似乎很奇怪*,当我尝试应用它们时,我无法在模拟它后设置状态。所以我决定改用组件的初始可选 props。
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();
});
*如果我改变代码的顺序怎么办?我需要重新组织考试吗?我的回答没有回答问题,但如果有人面临与我相同的问题,您可以使用此作为替代方法。
不更改为 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
}, [])
希望这有帮助!