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

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();
  });
});

关键注意点

  1. Storage的forEach回调参数顺序是**(value, key, index)**,模拟时必须严格遵循这个顺序,否则筛选逻辑会出错。
  2. 真实的forEach返回Promise<void>,所以模拟方法必须返回一个resolved的Promise,保证异步逻辑正常执行。
  3. 两种方案都完全脱离真实Storage实例,只通过模拟行为完成测试,符合单元测试的隔离原则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:25:36