Angular如何为new Map()创建的functionMap、ComponentMap编写单元测试
TourStoreServiceService 单元测试编写方案
测试基于Angular官方TestBed测试工具+Jest/Jasmine语法实现,核心逻辑是隔离外部依赖,逐一验证内部Map读写、状态变更、外部依赖调用的行为是否符合预期。
前置测试环境配置
首先mock服务依赖的StorageService,避免真实存储操作影响用例独立性:
import { TestBed } from '@angular/core/testing'; import { TourStoreServiceService } from './tour-store-service.service'; import { LOCAL_STORAGE } from './your-injection-token.path'; // 替换为项目中LOCAL_STORAGE注入令牌的实际路径 import { Type } from '@angular/core'; // 模拟存储服务 class MockStorageService { set = jest.fn(); // 若使用Jasmine,替换为jasmine.createSpy('set') } describe('TourStoreServiceService', () => { let service: TourStoreServiceService; let storage: MockStorageService; beforeEach(() => { TestBed.configureTestingModule({ providers: [ TourStoreServiceService, { provide: LOCAL_STORAGE, useClass: MockStorageService } ] }); service = TestBed.inject(TourStoreServiceService); storage = TestBed.inject(LOCAL_STORAGE) as unknown as MockStorageService; // 每个用例执行前清空mock调用记录 jest.clearAllMocks(); // Jasmine框架替换为.calls.reset() }); });
测试核心原则:
- 用例之间完全隔离,每个用例执行前重新初始化服务实例,避免Map残留数据干扰断言
- 所有外部依赖全部用mock替代,不操作真实本地存储,保证测试执行速度和结果稳定性
- 涉及随机值生成的逻辑通过桩代码固定返回值,避免随机结果导致的测试偶发失败
具体测试用例实现
1. 函数注册与查询逻辑(对应functionMap操作)
覆盖registerFnRef、getFnByKey两个方法的核心场景:
it('registerFnRef 需将传入的函数以指定key存入内部functionMap', () => { const testKey = 'testFunction'; const mockFn = () => 'test result'; service.registerFnRef(testKey, mockFn); // 断言存入的函数和原函数引用一致 expect(service.getFnByKey(testKey)).toBe(mockFn); }); it('getFnByKey 查询不存在的key时需返回undefined', () => { expect(service.getFnByKey('nonExistFnKey')).toBeUndefined(); }); it('registerFnRef 重复注册同一个key时需覆盖原有函数引用', () => { const testKey = 'duplicateFnKey'; const firstMockFn = () => 1; const secondMockFn = () => 2; service.registerFnRef(testKey, firstMockFn); service.registerFnRef(testKey, secondMockFn); expect(service.getFnByKey(testKey)).toBe(secondMockFn); });
2. 组件注册与查询逻辑(对应componentMap操作)
覆盖registerComponent、getComponentByKey两个方法的核心场景:
it('registerComponent 需将传入的组件类以指定key存入内部componentMap', () => { const testKey = 'testComponent'; // 模拟测试组件类 class MockTestComponent {} service.registerComponent(testKey, MockTestComponent as Type<unknown>); expect(service.getComponentByKey(testKey)).toBe(MockTestComponent); }); it('getComponentByKey 查询不存在的key时需返回undefined', () => { expect(service.getComponentByKey('nonExistComponentKey')).toBeUndefined(); }); it('registerComponent 重复注册同一个key时需覆盖原有组件引用', () => { const testKey = 'duplicateComponentKey'; class FirstMockComponent {} class SecondMockComponent {} service.registerComponent(testKey, FirstMockComponent as Type<unknown>); service.registerComponent(testKey, SecondMockComponent as Type<unknown>); expect(service.getComponentByKey(testKey)).toBe(SecondMockComponent); });
3. 会话ID生成逻辑(对应generateSessionId方法)
同时验证内部状态赋值、存储调用两个行为,通过桩代码替换uuid生成逻辑,避免随机值导致断言不稳定:
it('generateSessionId 需生成新会话ID、赋值给内部状态同时写入本地存储', () => { const fixedTestSessionId = 'fixed-session-id-001'; // 替换内部sessionGenerator的返回值,屏蔽uuid随机性 jest.spyOn(service as any, 'sessionGenerator', 'get').mockReturnValue(() => fixedTestSessionId); // Jasmine框架替换为: // spyOnProperty(service as any, 'sessionGenerator', 'get').and.returnValue(() => fixedTestSessionId); service.generateSessionId(); // 验证存储方法调用参数正确 expect(storage.set).toHaveBeenCalledTimes(1); expect(storage.set).toHaveBeenCalledWith('TOUR_SESSION_ID', fixedTestSessionId); // 验证内部sessionId状态赋值正确 expect((service as any).sessionId).toBe(fixedTestSessionId); });
说明:用例中通过
as any访问类的私有成员是单元测试的常规操作,不需要为了测试特意修改类成员的访问修饰符;如果项目规范严格禁止访问私有成员,可以给服务类增加一个只读的sessionId getter对外暴露值,再通过getter完成断言即可。
内容的提问来源于stack exchange,提问作者Tornado
相关产品推荐
相关产品推荐

