jest和enzyme中useState Hook如何设置初始状态?

问题描述 投票:0回答:11
目前我正在使用带有反应钩子的功能组件。但我无法完全测试

useState

 钩子。考虑这样的场景,在 
useEffect
 钩子中,我正在执行 API 调用并在 
useState
 中设置值。对于笑话/酶,我已经模拟了要测试的数据,但我无法在笑话中设置 
useState
 的初始状态值。

const [state, setState] = useState([]);


我想将初始状态设置为玩笑中的对象数组。我找不到任何类似于类组件的 setState 函数。

javascript reactjs enzyme react-hooks react-hooks-testing-library
11个回答
36
投票
您可以模拟

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


30
投票
首先,您不能在组件中使用解构。例如,您不能使用:

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.
    

13
投票
如果我没记错的话,你应该尽量避免模拟像

useState

useEffect
 这样的内置钩子。如果使用酶的 
invoke()
 很难触发状态变化,那么这可能表明您的组件将从分解中受益。


8
投票

解构解决方案

您不需要使用

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
    

6
投票
//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); });
    

6
投票
    下面的函数将返回状态
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, });
灵感:

转到


1
投票
我花了很多时间,但找到了在我的应用程序中测试多个 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: [], });
    

1
投票
我使用了多个

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 />); ....
    

0
投票
以下是您如何在没有酶的情况下轻松做到这一点。如果你使用Context,你甚至可以做到这一点。

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 是什么样子,看看渲染时你的评论状态是否是什么样子。


0
投票
我的目标是为

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();
  });
*如果我改变代码的顺序怎么办?我需要重新组织考试吗?

我的回答没有回答问题,但如果有人面临与我相同的问题,您可以使用此作为替代方法。


-1
投票

不更改为 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 }, [])

希望这有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.