Angular与Jasmine集成测试中Service触发延迟问题求助
问题:集成测试中确保Service在断言前触发值
组件代码
export class HomePage implements OnInit { public flag = false; constructor(private authService: AuthService) { console.log("HomePage Created"); this.authService.subject.subscribe(res => { this.flag = res; }) } ngOnInit() { } }
服务代码
export class AuthService { public subject = new BehaviorSubject(false); constructor(private storage: Storage) { console.log("AuthService created"); this.storage.create().then(() => { this.storage.set('token', true).then(() => { this.storage.get('token').then(token => { console.log("token sent"); this.subject.next(token); }) }) }); } }
测试代码
describe('HomePage', () => { let component: HomePage; let fixture: ComponentFixture<HomePage>; beforeEach(() => { TestBed.configureTestingModule({ imports: [IonicStorageModule.forRoot()], }); fixture = TestBed.createComponent(HomePage) component = fixture.componentInstance; fixture.detectChanges(); }); fit('should create', async () => { await fixture.whenStable() console.log("test run"); expect(component).toBeTruthy(); }); });
测试输出
24 05 2024 17:19:33.830:INFO [Chrome 115.0.0.0 (Linux x86_64)]: Connected on socket ZveuSQt62spdgsS6AAAF with id 45284113 LOG: 'AuthService created' Chrome 115.0.0.0 (Linux x86_64): Executed 0 of 4 SUCCESS (0 secs / 0 secs) LOG: 'HomePage Created' Chrome 115.0.0.0 (Linux x86_64): Executed 0 of 4 SUCCESS (0 secs / 0 secs) LOG: 'test run' Chrome 115.0.0.0 (Linux x86_64): Executed 0 of 4 SUCCESS (0 secs / 0 secs) LOG: 'token sent' Chrome 115.0.0.0 (Linux x86_64): Executed 1 of 4 (skipped 3) SUCCESS (0.056 secs / 0.04 secs) LOG: 'token sent' TOTAL: 1 SUCCESS
问题说明
从测试输出可见,token sent在测试运行完成后才打印。实际场景中,组件因未在测试断言前收到token,导致DOM组件无法更新。需要在不Mock服务的前提下,确保Service在断言执行前触发值,尝试过多种fixture.whenStable()和fixture.detectChanges()的组合均无效。
解决方案
核心问题是Ionic Storage的异步操作属于微任务队列,而fixture.whenStable()仅等待Angular自身的异步操作完成,不会主动清空所有微任务队列。以下是几种无需Mock服务的集成测试解决方案:
1. 手动触发微任务执行
在await fixture.whenStable()后,通过Promise.resolve()清空微任务队列,再触发变更检测:
fit('should create and receive token', async () => { await fixture.whenStable(); // 等待所有微任务(包括Storage操作)完成 await Promise.resolve(); // 触发变更检测更新DOM fixture.detectChanges(); console.log("test run"); expect(component.flag).toBe(true); expect(component).toBeTruthy(); });
2. 在beforeEach中等待Service的信号
通过监听AuthService的BehaviorSubject,等待其发出预期值后再初始化组件:
beforeEach(async () => { await TestBed.configureTestingModule({ imports: [IonicStorageModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(HomePage); component = fixture.componentInstance; const authService = TestBed.inject(AuthService); // 等待subject发送token值 await new Promise(resolve => { const sub = authService.subject.subscribe(value => { if (value === true) { sub.unsubscribe(); resolve(value); } }); }); fixture.detectChanges(); }); fit('should create', async () => { console.log("test run"); expect(component.flag).toBe(true); expect(component).toBeTruthy(); });
3. 修改Service暴露初始化Promise
如果允许修改服务代码,将构造函数内的异步逻辑封装为公开Promise,方便测试等待:
// 修改后的AuthService export class AuthService { public subject = new BehaviorSubject(false); public initPromise: Promise<void>; constructor(private storage: Storage) { console.log("AuthService created"); this.initPromise = this.storage.create().then(() => { return this.storage.set('token', true).then(() => { return this.storage.get('token').then(token => { console.log("token sent"); this.subject.next(token); }); }); }); } }
测试中直接等待该Promise:
beforeEach(async () => { await TestBed.configureTestingModule({ imports: [IonicStorageModule.forRoot()], }).compileComponents(); fixture = TestBed.createComponent(HomePage); component = fixture.componentInstance; const authService = TestBed.inject(AuthService); await authService.initPromise; fixture.detectChanges(); });
内容的提问来源于stack exchange,提问作者Juan Breinlinger
相关产品推荐
相关产品推荐

