如何对使用Redux selectors的React组件进行单元测试
解决方案
核心思路
你的组件渲染逻辑完全依赖Redux中存储的view值,测试时只要手动控制这个状态的返回值,分别校验两种场景下对应组件是否渲染即可。
步骤1:给子组件添加测试标识
先给两个视图组件加测试ID,方便测试时定位:
// Projects组件返回值部分修改 return ( <Wrapper> <div className="projects-toolbar" data-testid="projects-toolbar-container"> <Toolbar /> </div> {view === VIEW_TYPES.grid && <ProjectsGridView data-testid="projects-grid-view" />} {view === VIEW_TYPES.list && <ProjectsListView data-testid="projects-list-view" />} </Wrapper> );
步骤2:补充测试用例
你可以选择两种方案实现状态控制:
方案1:轻量mock useSelector(推荐)
直接mock Redux的hook返回值,不需要启动完整store:
// 测试文件顶部加mock import { useSelector, useDispatch } from 'react-redux'; import { VIEW_TYPES } from '你的常量文件路径'; import { selectView } from '你的selector路径'; import { projectsActions } from '你的action路径'; jest.mock('react-redux', () => ({ ...jest.requireActual('react-redux'), useSelector: jest.fn(), useDispatch: jest.fn(), })); const mockDispatch = jest.fn(); describe('Projects组件', () => { beforeEach(() => { useDispatch.mockReturnValue(mockDispatch); mockDispatch.mockClear(); }); test('renders toolbar container', () => { // 随便给一个view值不影响当前测试 useSelector.mockImplementation(selector => selector === selectView ? VIEW_TYPES.grid : undefined ); const { getByTestId } = setup(); expect(getByTestId('projects-toolbar-container')).toBeInTheDocument(); }); test('renders grid view when view type is grid', () => { useSelector.mockImplementation(selector => selector === selectView ? VIEW_TYPES.grid : undefined ); const { getByTestId, queryByTestId } = setup(); expect(getByTestId('projects-grid-view')).toBeInTheDocument(); // 同时校验列表视图不渲染 expect(queryByTestId('projects-list-view')).not.toBeInTheDocument(); }); test('renders list view when view type is list', () => { useSelector.mockImplementation(selector => selector === selectView ? VIEW_TYPES.list : undefined ); const { getByTestId, queryByTestId } = setup(); expect(getByTestId('projects-list-view')).toBeInTheDocument(); // 同时校验网格视图不渲染 expect(queryByTestId('projects-grid-view')).not.toBeInTheDocument(); }); // 额外可以测挂载时的dispatch调用是否符合预期 test('calls restore actions on mount', () => { useSelector.mockImplementation(selector => selector === selectView ? VIEW_TYPES.grid : undefined ); setup(); expect(mockDispatch).toHaveBeenCalledWith(projectsActions.viewRestore()); expect(mockDispatch).toHaveBeenCalledWith(projectsActions.filterRestore()); }); })
方案2:使用真实Redux Store测试
如果你的测试框架已经封装了带Redux Provider的render方法,可以直接传入初始state:
// 假设你的setup函数支持传入initialState参数 test('renders grid view when view type is grid', () => { const { getByTestId, queryByTestId } = setup({ initialState: { // 对应你的Redux state结构,设置view为grid projects: { view: VIEW_TYPES.grid } } }); expect(getByTestId('projects-grid-view')).toBeInTheDocument(); expect(queryByTestId('projects-list-view')).not.toBeInTheDocument(); });
注意事项
- 不要用
getByTestId校验不存在的元素,会直接抛出错误,校验不存在的元素用queryByTestId - 如果子组件本身有依赖,测试时可以按需mock子组件,避免子组件内部逻辑影响当前测试
内容的提问来源于stack exchange,提问作者I'm not human
相关产品推荐
相关产品推荐

