You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React不使用自定义Hook如何Mock测试组件localStorage逻辑

答案

你完全不需要额外编写自定义Hook,也不是必须用renderHook才能测试localStorage相关逻辑,直接Mock全局localStorage API,通过常规的组件渲染+用户交互模拟就能覆盖所有存取场景。


具体测试实现(以React Testing Library + Vitest/Jest为例)

前置Mock配置

在每个测试用例执行前重写localStorage的原生方法,避免用例间数据污染:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, beforeEach, vi } from 'vitest'; // 用Jest的话把vi替换成jest即可
import TodoList from './TodoList';

beforeEach(() => {
  // Mock localStorage的两个核心方法
  Storage.prototype.getItem = vi.fn();
  Storage.prototype.setItem = vi.fn();
  // 清空所有Mock的调用记录
  vi.clearAllMocks();
});

测试用例1:组件初始化读取localStorage缓存

it('组件挂载时正确读取localStorage中已存储的待办列表', () => {
  // 预设localStorage里的存储内容
  const mockStoredTasks = JSON.stringify([
    { taskName: '写单元测试', id: 1710000000000 }
  ]);
  // 让getItem调用返回预设值
  (Storage.prototype.getItem as ReturnType<typeof vi.fn>).mockReturnValue(mockStoredTasks);

  render(<TodoList />);

  // 断言页面渲染了缓存的待办项
  expect(screen.getByText('写单元测试')).toBeInTheDocument();
  // 断言确实调用了localStorage读取,且key正确
  expect(Storage.prototype.getItem).toHaveBeenCalledWith('localTasks');
});

测试用例2:新增待办时写入localStorage

it('添加新待办后,将最新列表同步写入localStorage', async () => {
  const user = userEvent.setup();
  // 初始化时localStorage无数据
  (Storage.prototype.getItem as ReturnType<typeof vi.fn>).mockReturnValue(null);
  render(<TodoList />);

  // 模拟用户输入待办内容、点击添加按钮
  await user.type(screen.getByPlaceholderText('enter a task'), '买牛奶');
  await user.click(screen.getByTestId('add-todo'));

  // 断言localStorage写入方法被调用,key正确,内容包含新增的待办
  expect(Storage.prototype.setItem).toHaveBeenCalledWith(
    'localTasks',
    expect.stringContaining('买牛奶')
  );
  // 断言输入框提交后被清空
  expect(screen.getByPlaceholderText('enter a task')).toHaveValue('');
});

测试用例3:删除待办时更新localStorage

it('删除待办后,同步更新localStorage中的列表', async () => {
  const user = userEvent.setup();
  const testTaskId = 1710000000000;
  // 预设localStorage里有一条待办
  const mockStoredTasks = JSON.stringify([
    { taskName: '要删除的待办', id: testTaskId }
  ]);
  (Storage.prototype.getItem as ReturnType<typeof vi.fn>).mockReturnValue(mockStoredTasks);

  render(<TodoList />);
  // 点击对应待办的删除按钮
  await user.click(screen.getByTestId(`delete-todo-${testTaskId}`));

  // 断言写入localStorage的是删除后的空列表
  expect(Storage.prototype.setItem).toHaveBeenCalledWith(
    'localTasks',
    JSON.stringify([])
  );
});

额外代码问题提醒

你更新版代码里的删除逻辑有bug:当前过滤条件写的是index !== todo.id,但id是时间戳,和数组索引没有对应关系,会出现删错项的问题,修正如下:

// 原错误逻辑
// const handleDelete = () => {
//   setTodoList((prevTodoList) => prevTodoList.filter((todo, index) => index !== todo.id));
// };

// 修正后
const handleDelete = (taskId: number) => {
  setTodoList((prevTodoList) => prevTodoList.filter((todo) => todo.id !== taskId));
};
// 按钮调用时记得传入id:onClick={() => handleDelete(todo.id)}

另外补充:是否抽取自定义Hook完全取决于你要不要复用这段localStorage存取逻辑,和测试没有关系,不需要为了测试特意抽离Hook,当前内联在组件里的逻辑完全可以通过上述方式完整覆盖。

内容的提问来源于stack exchange,提问作者goatstash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 06:27:26