如何测试 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=() => dispatch(authActions.logout())
,而不是onClick=dispatch(authActions.logout())
。
感谢@EstusFlask。我发布的测试代码不是完整的测试,只是我在调度时模拟/监视的尝试。我一直在使用act(() => 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()
模拟访问store
:const store = wrappedMenuDrawer.find(Provider).prop('store');
以上是关于如何测试 Redux 的 dispatch() 是不是已在 React Component (Jest + Enzyme) 中被调用的主要内容,如果未能解决你的问题,请参考以下文章