如何使用 useReducer 钩子测试组件?

Posted

技术标签:

【中文标题】如何使用 useReducer 钩子测试组件?【英文标题】:How test a component using the useReducer hook? 【发布时间】:2020-04-04 02:59:17 【问题描述】:

减速机

// src/reducers/FooReducer.js

export function FooReducer(state, action) 
  switch (action.type) 
    case 'update': 
      return action.newState;
    
// ... other actions
    default:
      throw new Error('Unknown action type');
  


组件

// src/components/BarComponent.js

export function BarComponent() 
  const [state, dispatch] = useReducer(FooReducer, []);

  return (
    state.map((item) => (<div />))
  );

测试

// src/components/BarComponent.test.js

it('should render as many divs as there are items', () => 
  act(() => 
    const  result  = renderHook(() => useReducer(FooReducer, [1]));
    const [, dispatch] = result.current;
    wrapper = mount(<BarComponent />);
    dispatch(type: 'update', newState: [1, 2, 3]);
  );

  expect(wrapper.find(div)).toHaveLength(3);
);

上面的测试示例不起作用,但用于演示我想要实现的目标。并且实际上会渲染 0 个 div,因为组件中声明的初始状态包含 0 个项目。

    为了测试目的,我将如何修改 reducer 的状态或更改其部署的 initialState?

    我习惯于 Redux reducer 在多个组件中使用,但是 useReducer 需要一个传递的 initialState... 这引发了一个问题:react-hook 的 reducer 是否可以作为单个实例通过多个组件使用,或者它始终是 2 个单独的实例?

【问题讨论】:

【参考方案1】:

在您的示例中,您正在尝试同时测试两件事,最好将其作为单独的测试进行:对 reducer 的单元测试,以及组件使用 reducer 的组件测试。

    我将如何修改 reducer 的状态或更改其部署时使用的 initialState 以进行测试?

与 Redux reducer 类似,您的 reducer 很容易进行单元测试,因为您将它作为纯函数导出。只需将您的初始状态传递给state 参数,并将您的操作传递给action

it('returns new state for "update" type', () => 
  const initialState = [1];
  const updateAction = type: 'update', newState: [1, 2, 3] ;
  const updatedState = fooReducer(initialState, udpateAction);
  expect(updatedState).toEqual([1, 2, 3]);
);

如果您愿意,也可以在 useReducer 的上下文中对其进行测试:

it('should render as many divs as there are items', () => 
  act(() => 
    const  result  = renderHook(() => useReducer(FooReducer, [1]));
    const [state, dispatch] = result.current;
    dispatch(type: 'update', newState: [1, 2, 3]);
  );

  expect(state).toEqual([1, 2, 3]);
  // or expect(state).toHaveLenth(3) if you prefer
);
    我习惯于 Redux reducer 在多个组件中使用,但 useReducer 需要传递的 initialState... 这引发了一个问题:react-hook 的 reducer 是否可通过多个组件作为单个实例使用,还是始终是 2 个单独的实例?

useReducer 与 Redux 的不同之处在于:您可以重用 reducer 本身,但如果您有多个 useReducers,则每个返回的 statedispatch 以及初始状态,将是单独的实例。

为了在 reducer 更新时测试您的 BarComponent 是否更新,您需要一种从组件内触发 dispatch 的方法,因为您在组件内调用 useReducer。这是一个例子:

export function BarComponent() 
  const [state, dispatch] = useReducer(FooReducer, []);

  const handleUpdate = () => dispatch(type: 'update', newState: [1, 2, 3])

  return (
    <>
      state.map((item) => (<div />))
      <button onClick=handleUpdate>Click to update state</button>
    </>
  );

it('should render as many divs as there are items', () => 
  wrapper = mount(<BarComponent />);

  expect(wrapper.find('div')).toHaveLength(0);

  wrapper.find('button').simulate('click');

  expect(wrapper.find('div')).toHaveLength(3);
);

这可能不太现实,因为我在组件本身中对新数组进行了硬编码,但希望它能给您带来想法!

【讨论】:

感谢您的第二个回答。至于第一个,我已经单独测试了我的减速器,无论如何,谢谢;)。第二个问题的答案确实引起了对我如何实现所有内容的担忧,也许使用 reducer 而不是 state 和 props 会过度杀伤力。 干得好..感谢您的说明性回答。

以上是关于如何使用 useReducer 钩子测试组件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 useReducer 钩子中发出异步服务器请求?

在 React 中使用 useReducer 钩子过滤列表?

如何使用 react-redux 钩子测试组件?

如何使用新的反应路由器钩子测试组件?

使用带有 useReducer() 钩子的 React Context API 有啥优点和缺点?

React Native Jest - 如何使用多个钩子测试功能组件?无法存根 AccessiblityInfo 模块