如何测试 Redux 的 dispatch() 是不是已在 React Component (Jest + Enzyme) 中被调用

Posted

技术标签:

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

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

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

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

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

【讨论】:

太棒了。谢谢你。我选择了第二个选项。我将makeTestStore() 添加到TestRoot 中,然后我可以使用test 块内的dispatch() 模拟访问storeconst store = wrappedMenuDrawer.find(Provider).prop('store');

以上是关于如何测试 Redux 的 dispatch() 是不是已在 React Component (Jest + Enzyme) 中被调用的主要内容,如果未能解决你的问题,请参考以下文章

测试调用 API 的 redux 操作

如何使用 Jest 测试 Thunk 操作?

还原 |如何正确连接到 redux 商店

使用 redux-promise 继续获得未定义的 'dispatch'

如何测试 redux-thunk 中间件异步功能?

如何修复 React Redux 和 React Hook useEffect 缺少依赖项:'dispatch'