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

如何在Angular Jasmine单元测试中Mock localforage

如何在Angular单元测试中Mock/Stub localforage

你的需求完全合理——我们只需要验证TokenService的业务逻辑,没必要去测试localforage本身的实现。下面给你两种实用方案,都是Angular测试场景里常用的方式:

方案一:封装localforage为Angular服务(推荐,符合依赖注入理念)

直接在TokenService里使用全局localForage会导致耦合过高,不利于测试。我们先把localforage封装成一个Angular服务,这样就能轻松注入Mock版本:

步骤1:创建封装服务

import { Injectable } from '@angular/core';
import localForage from 'localforage';

@Injectable({ providedIn: 'root' })
export class LocalForageService {
  setItem(key: string, value: any): Promise<any> {
    return localForage.setItem(key, value);
  }

  getItem(key: string): Promise<any> {
    return localForage.getItem(key);
  }

  // 后续需要用到的其他localforage方法,都可以在这里封装
}

步骤2:修改TokenService依赖注入封装服务

import { Injectable } from '@angular/core';
import { LocalForageService } from './local-forage.service';

@Injectable({ providedIn: 'root' })
export class TokenService {
  private TOKEN_KEY = 'id_token';

  constructor(private localForageService: LocalForageService) { }

  setAccessToken(token: string): Promise<any> {
    return this.localForageService.setItem(this.TOKEN_KEY, token);
  }

  getAccessToken(): Promise<any> {
    return this.localForageService.getItem(this.TOKEN_KEY);
  }
}

注意:我修正了原代码的类型错误——localforage.setItem和getItem都是异步方法,返回的是Promise,不能直接标注返回值为string。

步骤3:在测试中Mock封装服务

在token.service.spec.ts里,用TestBed替换成我们自己的Mock类:

import { TestBed } from '@angular/core/testing';
import { TokenService } from './token.service';
import { LocalForageService } from './local-forage.service';

// 自定义Mock服务,模拟localforage的存储逻辑
class MockLocalForageService {
  private storage: Record<string, any> = {};

  setItem(key: string, value: any): Promise<any> {
    this.storage[key] = value;
    return Promise.resolve(value);
  }

  getItem(key: string): Promise<any> {
    return Promise.resolve(this.storage[key]);
  }
}

describe('TokenService', () => {
  let service: TokenService;
  let mockLocalForage: MockLocalForageService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        TokenService,
        { provide: LocalForageService, useClass: MockLocalForageService } // 替换为Mock类
      ]
    });
    service = TestBed.inject(TokenService);
    mockLocalForage = TestBed.inject(LocalForageService) as unknown as MockLocalForageService;
  });

  it('should store access token correctly', async () => {
    const testToken = 'test-token-123';
    await service.setAccessToken(testToken);
    
    const storedToken = await mockLocalForage.getItem('id_token');
    expect(storedToken).toBe(testToken);
  });

  it('should retrieve access token correctly', async () => {
    const testToken = 'test-token-456';
    await mockLocalForage.setItem('id_token', testToken);
    
    const retrievedToken = await service.getAccessToken();
    expect(retrievedToken).toBe(testToken);
  });
});

方案二:直接Mock全局localforage(适合不想修改现有代码的场景)

如果你暂时不想封装服务,也可以直接在测试中替换全局的localforage对象:

import { TestBed } from '@angular/core/testing';
import { TokenService } from './token.service';

// 声明全局localForage类型,避免TypeScript报错
declare var localForage: any;

describe('TokenService', () => {
  let service: TokenService;
  let mockSetItem: jasmine.Spy;
  let mockGetItem: jasmine.Spy;

  beforeEach(() => {
    // 创建Spy函数,模拟localforage的方法
    mockSetItem = jasmine.createSpy('setItem').and.returnValue(Promise.resolve());
    mockGetItem = jasmine.createSpy('getItem').and.returnValue(Promise.resolve('mock-token'));

    // 替换全局的localForage对象
    (window as any).localForage = {
      setItem: mockSetItem,
      getItem: mockGetItem
    };

    TestBed.configureTestingModule({
      providers: [TokenService]
    });
    service = TestBed.inject(TokenService);
  });

  it('should call localForage.setItem with correct parameters', async () => {
    const testToken = 'test-token-789';
    await service.setAccessToken(testToken);
    
    expect(mockSetItem).toHaveBeenCalledWith('id_token', testToken);
  });

  it('should call localForage.getItem and return expected token', async () => {
    const result = await service.getAccessToken();
    
    expect(mockGetItem).toHaveBeenCalledWith('id_token');
    expect(result).toBe('mock-token');
  });
});

小提示:方案一的封装方式更符合Angular最佳实践,它降低了代码耦合,以后如果要替换存储方案(比如换成sessionStorage),只需要修改封装服务即可,不需要改动TokenService的业务代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:05:22