Ionic Storage v3单元测试报错:_this.storage.forEach不是函数
解决Ionic StorageService单元测试中forEach模拟失效的问题
你遇到的TypeError: _this.storage.forEach is not a function错误,本质是创建的Storage模拟对象没有正确实现forEach方法——@ionic/storage-angular的Storage类的forEach是一个接受回调、返回Promise的异步方法,不是普通数组的forEach,必须在模拟对象中还原这个行为。
以下是两种可行的模拟方案,都不需要依赖真实Storage实例:
方案1:创建完整Mock类
适合Service用到Storage多个方法的场景,模拟更贴近真实行为:
import { TestBed } from '@angular/core/testing'; import { StorageService } from './storage.service'; import { Storage } from '@ionic/storage-angular'; // 自定义Storage模拟类,实现核心方法 class MockStorage { private mockData: Record<string, any> = {}; // 模拟Storage的forEach:遍历预设数据,调用回调,返回Promise forEach(callback: (value: any, key: string, index: number) => void): Promise<void> { let index = 0; for (const [key, value] of Object.entries(this.mockData)) { callback(value, key, index); index++; } return Promise.resolve(); } // 若Service用到set方法,同步模拟实现 set(key: string, value: any): Promise<any> { this.mockData[key] = value; return Promise.resolve(value); } } describe('StorageService', () => { let service: StorageService; let storage: MockStorage; beforeEach(async () => { await TestBed.configureTestingModule({ providers: [ StorageService, { provide: Storage, useClass: MockStorage } ] }).compileComponents(); service = TestBed.inject(StorageService); storage = TestBed.inject(Storage) as unknown as MockStorage; }); it('should return only entries with keys starting with "data-"', async () => { // 预设测试数据 await storage.set('data-user', { id: 1, name: 'John' }); await storage.set('data-settings', { theme: 'dark' }); await storage.set('auth-token', 'xyz123'); // 此条目会被过滤 // 调用待测试方法 const filteredData = await service.getData(); // 验证结果 expect(Object.keys(filteredData)).toEqual(['data-user', 'data-settings']); expect(filteredData['data-user']).toEqual({ id: 1, name: 'John' }); }); });
方案2:用Jasmine SpyObj快速模拟
适合只需要模拟forEach单个方法的场景,更轻量化:
import { TestBed } from '@angular/core/testing'; import { StorageService } from './storage.service'; import { Storage } from '@ionic/storage-angular'; describe('StorageService', () => { let service: StorageService; let mockStorage: jasmine.SpyObj<Storage>; beforeEach(async () => { // 创建Storage的Spy对象,指定要模拟的方法 mockStorage = jasmine.createSpyObj('Storage', ['forEach']); // 模拟forEach的行为:遍历预设测试数据,调用传入的回调 mockStorage.forEach.and.callFake((callback: (value: any, key: string) => void) => { const testEntries = { 'data-user': { id: 1, name: 'John' }, 'data-settings': { theme: 'dark' }, 'auth-token': 'xyz123' }; Object.entries(testEntries).forEach(([key, value]) => callback(value, key)); return Promise.resolve(); }); await TestBed.configureTestingModule({ providers: [ StorageService, { provide: Storage, useValue: mockStorage } ] }).compileComponents(); service = TestBed.inject(StorageService); }); it('should filter out non-"data-" prefixed entries', async () => { const result = await service.getData(); expect(result).toEqual({ 'data-user': { id: 1, name: 'John' }, 'data-settings': { theme: 'dark' } }); // 验证forEach确实被调用 expect(mockStorage.forEach).toHaveBeenCalled(); }); });
关键注意点
- Storage的
forEach回调参数顺序是**(value, key, index)**,模拟时必须严格遵循这个顺序,否则筛选逻辑会出错。 - 真实的
forEach返回Promise<void>,所以模拟方法必须返回一个resolved的Promise,保证异步逻辑正常执行。 - 两种方案都完全脱离真实Storage实例,只通过模拟行为完成测试,符合单元测试的隔离原则。
内容的提问来源于stack exchange,提问作者Dana
相关产品推荐
相关产品推荐

