【问题标题】:How to test that Redux's dispatch() has been called in React Component (Jest + Enzyme)如何测试 Redux 的 dispatch() 是否已在 React Component (Jest + Enzyme) 中被调用
【发布时间】:2020-10-15 12:09:43
【问题描述】:

我有很多调用 dispatch() (Redux) 的功能性 React 组件。 dispatch() 函数本身是由 react-redux 的 useDispatch() 钩子传入的。

作为一个简化的例子:

const LogoutButton: FC = () => {
  const dispatch: Dispatch = useDispatch();
  
  return (
   <button onClick={() => {
     console.log("Clicked...") // To check that onClick() simulation is working
     dispatch(authActions.logout())  
   }}>
   LOGOUT
   </button> 
  )
}

使用 Jest 和 Enzyme,我需要做什么才能断言 expect(dispatch).toHaveBeenCalledWith(authActions.logout)

我没有模拟商店或使用 redux-mock-store 的功能。相反,我将组件包装在我为测试而制作的 Root 组件中。它与我真正的 Root 组件相同,但需要道具来设置测试的初始存储(和 history.location):

const TestRoot: FC<RootProps> = ({ children, initialState = {}, initialEntries = defaultLocation }) => {
  const store = createStore(reducers, initialState, applyMiddleware(thunk));

  return (
    <Provider store={store}>
      <MemoryRouter initialEntries={initialEntries}>
        <ScrollToTop />
        <StylesProvider jss={jss}>
          <ThemeProvider theme={theme}>{children}</ThemeProvider>
        </StylesProvider>
      </MemoryRouter>
    </Provider>
  );
};

在测试中用于设置 Enzyme-wrapped 组件,如:

wrappedLogoutButton = mount(
  <TestRoot initialState={initialState}>
    <LogoutButton />
  </TestRoot>
);

这个设置对我来说很有效(到目前为止),如果我不需要,我不想更改它。我确实尝试将一个 redux-mock-store 模拟存储注入到 TestRoot 中,但这搞砸了我编写的每一个测试套件。

我尝试了多种方法来模拟或监视dispatch()useDispatch(),但我无法看到模拟被调用。 onClick() 模拟正在运行,因为我可以看到正在记录“点击...”。这是一个示例测试(真实代码,不是简化示例):

test('should log learner out', () => {
    const event = {
      preventDefault: () => {},
      target: { textContent: en.common['log-out-title'] } as unknown,
    } as React.MouseEvent<HTMLButtonElement, MouseEvent>;

    const wrappedLogOut = wrappedMenuDrawer.find(ListItem).at(3).find('a');

    // @ts-ignore
    act(() => wrappedLogOut.prop('onClick')(event));

    // TODO: test authService.logout is called
    // assert that dispatch was called with authActions.logout()
    expect(amplitude.logAmpEvent).toHaveBeenCalledWith('from main menu', { to: 'LOGOUT' }); // This assertion passes
  });

根据文档、Medium 帖子和 Stack Overflow 上的类似问题,我尝试过在调度时模拟/监视的方法/变体,包括:

import * as reactRedux from 'react-redux'
const spy = jest.spyOn(reactRedux, 'useDispatch'
import store from '../../store';
const spy = jest.spyOn(store, 'dispatch')
const mockUseDispatch = jest.fn();
const mockDispatch = jest.fn();

jest.mock('react-redux', () => ({
  useDispatch: () => mockUseDispatch.mockReturnValue(mockDispatch),
}));

// or...

jest.mock('react-redux', () => ({
  useDispatch: () => mockUseDispatch,
}));

mockUseDispatch.mockReturnValue(mockDispatch) // inside the actual test
  

【问题讨论】:

  • 我需要做什么才能断言 - 请参阅enzymejs.github.io/enzyme/docs/api/ReactWrapper/simulate.htmlonClick() 模拟正在工作,因为我可以看到“Clicked...”被记录 - 它不是,并且在您发布的代码中没有单击模拟。 console.log 和 dispatch 是在渲染时调用的,而不是在点击时调用的,顺便说一句,onClick 有语法错误,它不会编译。应该是onClick={() =&gt; dispatch(authActions.logout())},而不是onClick={dispatch(authActions.logout())}
  • 感谢@EstusFlask。我发布的测试代码不是完整的测试,只是我在调度时模拟/监视的尝试。我一直在使用act(() =&gt; wrappedButton.prop('onClick')()); 的变体来模拟点击。您发现的语法错误(很好的发现)是 StackOverflow 帖子上的错字,但不是我的实际代码。我将修正错字并从我的代码中添加一个实际测试。如果您仍然认为模拟点击有问题,请告诉我,但我认为没有。
  • 一个地方是a,另一个地方是button我一直在使用变体 - 究竟是什么变体?直接调用 onClick 并不是真正的点击模拟(但可能还可以)。此外,您不需要 act 和 Enzyme。您可以为您的问题提供stackoverflow.com/help/mcve 吗?该问题仅包含截断且不一致的 sn-ps,无法说明您做错了什么。如果有断言失败的错误消息,请发布。

标签: reactjs redux react-redux jestjs enzyme


【解决方案1】:

您可以通过多种方式做到这一点。阿法克:

  • 你可以模拟 useDispatch 钩子来返回你自己的假调度函数。 我在我自己的 React 库的测试代码中使用了帮助管理绑定调度到动作。这里是hooks.test.tsx

  • 由于您使用测试 React 存储包装组件,因此您也可以直接将 store.dispatch 替换为假函数。请参阅here 进行讨论。

第一个的好处是你可以根据需要轻松地从 react-redux 中模拟更多的东西。

【讨论】:

  • 太棒了。谢谢你。我选择了第二个选项。我将makeTestStore() 添加到TestRoot 中,然后我可以使用test 块内的dispatch() 模拟访问storeconst store = wrappedMenuDrawer.find(Provider).prop('store');
猜你喜欢
  • 2021-07-26
  • 2018-08-28
  • 2020-05-22
  • 2019-01-27
  • 2018-09-14
  • 2020-11-20
  • 1970-01-01
  • 2019-02-03
  • 2020-03-30
相关资源
最近更新 更多