如何为修改状态数组的React函数编写单元测试?
为handleSelect函数编写单元测试的思路与示例
核心测试场景
先明确要覆盖的关键逻辑:
- 未选中的图片,点击后ID被加入
imagesToDelete数组 - 已选中的图片,点击后ID从
imagesToDelete数组中移除 - 空列表状态下,点击图片能正确添加第一个ID
- 移除唯一选中的ID后,列表回到空状态
- 多图片选择时,操作单个图片不影响其他选中项
第一步:提取纯逻辑简化测试
把handleSelect里的状态更新逻辑抽成纯函数,脱离组件状态依赖,更容易测试:
// 提取纯逻辑:输入当前选中ID列表和目标图片ID,返回新的选中列表 const toggleImageSelection = (currentSelectedIds, imageId) => { if (!currentSelectedIds.includes(imageId)) { return [...currentSelectedIds, imageId]; } else { return currentSelectedIds.filter(id => id !== imageId); } }; // 组件中修改handleSelect调用这个纯函数(用函数式更新避免状态快照问题) const handleSelect = (image) => { setImagesToDelete(prevState => toggleImageSelection(prevState, image.id)); };
纯逻辑测试用例(Jest)
test('添加未选中的图片ID', () => { const currentIds = ['img-1', 'img-2']; const newId = 'img-3'; expect(toggleImageSelection(currentIds, newId)).toEqual(['img-1', 'img-2', 'img-3']); }); test('移除已选中的图片ID', () => { const currentIds = ['img-1', 'img-2', 'img-3']; const removedId = 'img-2'; expect(toggleImageSelection(currentIds, removedId)).toEqual(['img-1', 'img-3']); }); test('空列表添加第一个ID', () => { expect(toggleImageSelection([], 'img-1')).toEqual(['img-1']); }); test('移除唯一ID后列表为空', () => { expect(toggleImageSelection(['img-1'], 'img-1')).toEqual([]); });
第二步:测试组件交互(React Testing Library)
除了逻辑测试,还要验证用户交互后的实际表现(比如选中后显示图标):
import { render, screen, fireEvent } from '@testing-library/react'; import ImageGallery from './ImageGallery'; // 替换为你的组件文件名 test('点击未选中图片,显示选中图标', () => { const testImages = [ { id: 'img-1', image: 'test-1.jpg' }, { id: 'img-2', image: 'test-2.jpg' } ]; render(<ImageGallery images={testImages} />); // 获取图片容器并点击 const firstImageContainer = screen.getByAltText('Placeholder').parentElement; fireEvent.click(firstImageContainer); // 验证选中图标出现(需给SelectIcon添加data-testid="select-icon") expect(screen.getByTestId('select-icon')).toBeInTheDocument(); }); test('点击已选中图片,隐藏选中图标', () => { const testImages = [{ id: 'img-1', image: 'test-1.jpg' }]; render(<ImageGallery images={testImages} />); const imageContainer = screen.getByAltText('Placeholder').parentElement; // 第一次点击选中 fireEvent.click(imageContainer); expect(screen.getByTestId('select-icon')).toBeInTheDocument(); // 第二次点击取消选中 fireEvent.click(imageContainer); expect(screen.queryByTestId('select-icon')).not.toBeInTheDocument(); });
测试要点提醒
- 优先测试用户能看到的行为(图标显示/隐藏),而非直接断言内部状态,这更贴近真实用户操作场景
- 使用函数式更新
setImagesToDelete(prev => ...),避免状态快照导致的逻辑错误,同时让测试更可靠 - 如果需要直接验证状态变化,可以用
renderHook测试封装后的状态逻辑钩子
内容的提问来源于stack exchange,提问作者npfist
相关产品推荐
相关产品推荐

