如何在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
相关产品推荐
相关产品推荐

