如何在笑话和酶中为 useState Hook 设置初始状态?

IT技术 javascript reactjs enzyme react-hooks react-hooks-testing-library
2021-04-02 18:49:21

目前我使用带有react-hooks的功能组件。但我无法useState完全测试钩子。考虑这样一个场景,在useEffect钩子中,我正在执行 API 调用并在useState. 对于玩笑/酶,我模拟了要测试的数据,但我无法useState在玩笑中设置初始状态值

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

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

5个回答

您可以模拟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

使用一系列的 mockImplementationOnce() 模拟多个 useState。jestjs.io/docs/en/mock-functions.html#mock-return-values
2021-05-22 18:49:21
如果我在组件中有多个 useState 语句怎么办?
2021-05-26 18:49:21
如何获得返回状态值,有可能吗?下面这样const [formSend, setArrayValues] = React.useState();
2021-06-05 18:49:21
@BenButterworth 你找到了答案吗?我和你有同样的情况——我不能模拟 useState,因为我在我的组件中使用它;而且我不能使用 mockImplementationOnce,因为我不确定 useState 将被调用多少次。
2021-06-18 18:49:21
如果您不知道 useState 的调用顺序怎么办?我有一个复杂的应用程序,有很多 useStates。我现在可能不应该测试实现。
2021-06-20 18:49:21

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

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.
我也不明白为什么我们应该使用 React.useState,有人可以解释一下吗?
2021-05-22 18:49:21
这达到了什么目的?useState是测试不应该知道或关心的实现细节。使用 props 并像用户一样与 UI 交互,将组件的状态间接设置为黑盒。您建议避免解构,这是一种常见的 React 习语,纯粹是为了帮助注入模拟,这一事实是一个巨大的危险信号,表明测试对组件的内部了解太多。
2021-06-04 18:49:21
你能详细说明为什么我们不能使用解构吗?因为据我所知,它是 React 组件的合法代码。
2021-06-06 18:49:21
@Asking 您需要用 jest.fn() 替换 useState。此解决方案涉及覆盖引用:React.useState = jest.fn()如果它被解构了,那么你在你的组件中调用了一个独立的函数,并且不能用 jest 函数替换它。
2021-06-10 18:49:21

如果我没记错的话,您应该尽量避免模拟useStateuseEffect. 如果使用酶的 很难触发状态变化invoke(),那么这可能表明您的组件将从分解中受益。

确切地。不是模拟状态,而是通过 UI 与组件交互和/或模拟外部 API 响应来触发状态更改。钩子是实现细节。
2021-06-05 18:49:21
测试实现细节比我能说的更好——避免测试实现细节是为了验证用户看到的功能,而不是短视的逐行行为。
2021-06-12 18:49:21
@ggorlen 我想我明白你在说什么。但是如果我无法useEffect通过模拟用户交互来触发逻辑怎么办?例如,内部的某些逻辑useEffect仅在组件安装时运行一次
2021-06-14 18:49:21
当然,useEffect在组件安装时运行,但几乎总是有某种可观察的效果迟早对用户可见。测试一下。如果useEffect触发fetch对 API调用以获取用户帖子的列表,我会拦截它并模拟回复,然后断言被模拟的帖子显示在 UI 中。这些都不涉及对组件是功能性的而不是类或用途的任何想法useEffect没有必要模拟 React。
2021-06-14 18:49:21
@ggorlen 所以不需要测试 useEffect() 逻辑?
2021-06-20 18:49:21
  • 下面的函数将返回状态
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, });

灵感:转到

对我有用,谢谢。我也使用import React, { useState } from 'react';,因此您可以useState通过这种方法自由使用扩展版本
2021-05-26 18:49:21
你如何将 reactMock 传递给安装的组件?
2021-05-27 18:49:21
您不必这样做,因为您在 React 钩子中模拟 useState 函数
2021-05-31 18:49:21
我只用 useState() 尝试过,但对我不起作用
2021-06-09 18:49:21
在你的代码中,你必须使用 React.useState() 来完成这项工作,否则它将无法工作。我们如何在没有 React 的情况下直接使用 useState()?
2021-06-12 18:49:21
//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);
});