js单元测试中应mock localstorage/sessionstorage以避免副作用:用内存对象替代原生api,通过object.defineproperty重写属性,配合beforeeach清空或重建实例,并可模拟异常如quota_exceeded_err。

在 JS 单元测试或开发调试中,直接依赖真实的 localStorage 和 sessionStorage 会带来副作用(如污染全局状态、跨测试干扰、无法模拟异常场景等)。因此,常用 Mock 方式隔离并可控地模拟它们的行为。核心思路是:在测试环境(如 Jest)中,用内存对象替代原生 API,并重写 window.localStorage 等属性的 getter/setter 或直接替换为自定义对象。
使用 Jest 自带的 mock 实现(推荐)
Jest 默认会在每个测试文件中提供干净的 jsdom 环境,但其内置的 localStorage 是只读空对象。需手动 mock 成可读写的内存实现:
- 在测试文件顶部或
setupTests.js中添加:
// setupTests.js 或 test 文件内
const createMockStorage = () => {
let store = {};
return {
setItem(key, value) {
store[key] = String(value);
},
getItem(key) {
return store[key] ?? null;
},
removeItem(key) {
delete store[key];
},
clear() {
store = {};
},
key(index) {
return Object.keys(store)[index] || null;
},
length: Object.keys(store).length,
};
};
Object.defineProperty(window, 'localStorage', {
value: createMockStorage(),
writable: true,
configurable: true,
});
Object.defineProperty(window, 'sessionStorage', {
value: createMockStorage(),
writable: true,
configurable: true,
});
```
- 每次调用
createMockStorage()都生成独立实例,确保测试间不共享数据; - 支持
key()和length属性,兼容标准用法; - 注意:必须设
writable: true,否则后续测试中无法重新赋值(如清空后重建)。
在 beforeEach 中重置存储(避免测试污染)
即使用了独立实例,若多个测试共用同一 mock 对象(比如未在 beforeEach 中重建),仍可能互相影响。建议在每个测试前清空:
beforeEach(() => {
window.localStorage.clear();
window.sessionStorage.clear();
});
```
- 也可在
beforeEach中重新 assign 新的 mock 实例,更彻底隔离; - 对异步测试(如等待
setItem后再读取),mock 是同步的,无需额外处理时序问题。
模拟异常行为(如 QUOTA_EXCEEDED_ERR)
真实环境中,存储超限时会抛出错误。Mock 可主动触发这类边界情况,验证代码健壮性:
```js// 替换 localStorage.setItem 为抛错版本
Object.defineProperty(window.localStorage, 'setItem', {
value: jest.fn().mockImplementation(() => {
throw new DOMException('Quota exceeded', 'QuotaExceededError');
}),
writable: true,
});
```
- 用
jest.fn().mockImplementation()精确控制某次调用行为; - 可结合
mockImplementationOnce实现“第一次失败、第二次成功”的序列; - 记得在
afterEach恢复原始方法,或使用jest.restoreAllMocks()。
封装成可复用的工具函数(提升维护性)
把 mock 逻辑抽成函数,方便多项目复用:
```js// test-utils/storageMock.js
export const mockStorage = (type = 'localStorage') => {
const store = {};
const storage = {
setItem(key, value) { store[key] = String(value); },
getItem(key) { return store[key] ?? null; },
removeItem(key) { delete store[key]; },
clear() { Object.keys(store).forEach(k => delete store[k]); },
key(i) { return Object.keys(store)[i] ?? null; },
length: Object.keys(store).length,
};
Object.defineProperty(window, type, {
value: storage,
writable: true,
configurable: true,
});
return storage;
};
// 在测试中:
import { mockStorage } from './test-utils/storageMock';
beforeEach(() => {
mockStorage('localStorage');
mockStorage('sessionStorage');
});
```
- 返回 storage 实例,便于在测试中直接调用方法(如断言内部状态);
- 支持按需 mock 其中一个,不强制两者同时替换。











