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

如何使用Jest为自定义useLocalStorage Hook编写单元测试

Jest 测试自定义Hook useLocalStorage 实操方案

前置准备

这个Hook是React自定义Hook,不能直接调用测试,需要搭配@testing-library/react提供的renderHook工具挂载测试。Jest默认带的jsdom环境已经内置了localStorage的mock实现,不需要额外引入第三方mock库,只需要在每个用例执行前清空localStorage,避免不同用例的缓存互相干扰即可。

必须覆盖的测试场景

  • SSR场景(window对象不存在)下初始化,直接返回传入的initialValue,不会调用localStorage相关方法
  • 浏览器环境下,localStorage中没有对应key的缓存时,初始化返回initialValue
  • 浏览器环境下,localStorage中存在对应key的合法缓存时,初始化正确反序列化JSON返回存储值
  • 读取localStorage抛出异常时(比如存储内容是非法JSON、localStorage被浏览器权限禁用),能捕获错误打印日志,正常返回initialValue不崩溃
  • 调用setValue传入普通值时,能正确更新内部state,同时将值序列化后写入localStorage对应key
  • 调用setValue传入更新函数时,能基于当前存储值计算新值,同步更新state和localStorage
  • 写入localStorage抛出异常时(比如存储配额满、无写入权限),能捕获错误打印日志,不阻塞代码执行
  • SSR场景下调用setValue,只会更新内部state,不会尝试访问localStorage的setItem方法

完整测试代码

首先在测试文件顶部配置测试环境,写通用的重置逻辑:

import { renderHook, act } from '@testing-library/react';
// 替换成你项目中useLocalStorage文件的实际引入路径
import { useLocalStorage } from './useLocalStorage';

beforeEach(() => {
  // 每个用例执行前清空localStorage
  window.localStorage.clear();
  // mock console.log 避免错误信息污染测试输出
  jest.spyOn(console, 'log').mockImplementation(() => {});
});

afterEach(() => {
  // 每个用例执行后还原所有mock
  jest.restoreAllMocks();
});

逐个场景写测试用例:

test('SSR环境window不存在时,初始化返回初始值', () => {
  const originalWindow = global.window;
  // 模拟SSR下window被销毁的场景
  delete global.window;

  const { result } = renderHook(() => useLocalStorage('testKey', 'defaultValue'));
  const [storedValue] = result.current;

  expect(storedValue).toBe('defaultValue');
  // 还原window对象避免影响其他用例
  global.window = originalWindow;
});

test('localStorage无对应缓存时,初始化返回传入的初始值', () => {
  const { result } = renderHook(() => useLocalStorage('testKey', 1234));
  const [storedValue] = result.current;
  expect(storedValue).toBe(1234);
});

test('localStorage有对应合法缓存时,初始化正确解析返回存储值', () => {
  const cacheValue = { username: 'test', age: 20 };
  window.localStorage.setItem('testKey', JSON.stringify(cacheValue));

  const { result } = renderHook(() => useLocalStorage('testKey', {}));
  const [storedValue] = result.current;
  expect(storedValue).toEqual(cacheValue);
});

test('读取localStorage抛错时,捕获错误返回初始值', () => {
  // mock getItem抛错模拟异常场景
  jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => {
    throw new Error('localStorage is disabled');
  });

  const { result } = renderHook(() => useLocalStorage('testKey', 'fallbackVal'));
  const [storedValue] = result.current;

  expect(storedValue).toBe('fallbackVal');
  expect(console.log).toHaveBeenCalledWith(expect.any(Error));
});

test('调用setValue传入普通值时,同步更新state和localStorage', () => {
  const { result } = renderHook(() => useLocalStorage('testKey', 'initVal'));
  const [, setValue] = result.current;

  // state更新必须包裹在act中,符合React的渲染规则
  act(() => {
    setValue('newVal');
  });

  const [updatedVal] = result.current;
  expect(updatedVal).toBe('newVal');
  expect(JSON.parse(window.localStorage.getItem('testKey'))).toBe('newVal');
});

test('调用setValue传入更新函数时,基于当前值计算新值并存储', () => {
  const { result } = renderHook(() => useLocalStorage('testKey', 0));
  const [, setValue] = result.current;

  act(() => {
    setValue(prev => prev + 10);
  });

  const [updatedVal] = result.current;
  expect(updatedVal).toBe(10);
  expect(JSON.parse(window.localStorage.getItem('testKey'))).toBe(10);
});

test('写入localStorage抛错时,捕获错误不导致代码崩溃', () => {
  // mock setItem抛错模拟写入异常
  jest.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
    throw new Error('storage quota exceeded');
  });

  const { result } = renderHook(() => useLocalStorage('testKey', 'initVal'));
  const [, setValue] = result.current;

  expect(() => {
    act(() => {
      setValue('writeFailVal');
    });
  }).not.toThrow();
  expect(console.log).toHaveBeenCalledWith(expect.any(Error));
});

注意:所有触发React state更新的操作(比如这里的setValue调用)必须包裹在act()函数内,否则会收到React的测试警告,且拿到的state值可能不是更新后的最新值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:57:14