使用Jasmine Marble测试Observable时无法更新值的问题排查
问题分析
你遇到的核心问题是组件在初始化时已经订阅了初始的alert$ Observable,后续测试中修改alertService.alert$的指向并不会让组件重新订阅新的Observable,所以组件永远只会收到第一个测试用例的初始值。
具体来说,你的第二个beforeEach里调用了fixture.detectChanges(),这会触发组件的ngOnInit生命周期钩子,此时组件会订阅当时的alertService.alert$(也就是testAlertGood对应的Observable)。之后在测试用例里替换alertService.alert$为新的cold Observable,组件的订阅关系并没有更新,自然不会收到新值。
解决方案
调整测试流程,在每个测试用例中先设置好alert$的目标值,再触发组件的初始化(即调用fixture.detectChanges()),这样组件订阅的就是当前测试用例需要的Observable。
修改后的测试代码
describe('AlertsComponent', () => { let component: AlertsComponent; let fixture: ComponentFixture<AlertsComponent>; let alertService: any; let testAlertGood: Alert = { type: AlertType.Success, title: 'Test title', message: 'Test message', forceAction: false }; let testAlertBad: String = 'bad alert'; let testAlertNoTitle: Alert = { type: AlertType.Success, title: null, message: 'Test message', forceAction: false }; beforeEach(async(() => { alertService = jasmine.createSpy('AlertService'); // 这里可以先给一个默认值,或者留空,每个测试用例再覆盖 alertService.alert$ = cold('a', { a: testAlertGood }); TestBed.configureTestingModule({ declarations: [ AlertsComponent ], schemas: [ NO_ERRORS_SCHEMA ], providers: [ { provide: Router, useClass: class { navigate = jasmine.createSpy('navigate'); } }, { provide: AlertService, useValue: alertService } ] }) .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AlertsComponent); component = fixture.componentInstance; // 移除这里的fixture.detectChanges(),不要提前初始化组件 }); fit('should create', () => { // 初始化组件 fixture.detectChanges(); expect(component).toBeTruthy(); }); it('should display an alert if the alert$ observable has an Alert value', async () => { // 这里alert$是初始的testAlertGood,直接初始化组件 fixture.detectChanges(); getTestScheduler().flush(); fixture.detectChanges(); const alertElements = fixture.debugElement.queryAll(By.css('.alert-container')); const alertIconContainer = fixture.debugElement.query(By.css('.icon-container')); const alertIconClass = fixture.debugElement.query(By.css('#alert-icon')); const alertTitle = fixture.debugElement.query(By.css('.title')); const alertBody = fixture.debugElement.query(By.css('.body')); expect(alertElements.length).toBe(1); expect(alertIconContainer.nativeElement.getAttribute('class')).toContain('bg-success'); expect(alertIconClass.nativeElement.getAttribute('class')).toContain('fa-check-circle'); expect(alertTitle.nativeElement.innerText).toContain('Test title'); expect(alertBody.nativeElement.innerText).toContain('Test message'); }); it('should hide the title p tag if the Alert.title is null', async () => { // 先设置当前测试需要的alert$值 alertService.alert$ = cold('a', { a: testAlertNoTitle }); // 再初始化组件,让它订阅新的Observable fixture.detectChanges(); getTestScheduler().flush(); fixture.detectChanges(); const alertTitle = fixture.debugElement.query(By.css('.title')); expect(alertTitle).toBeNull(); }); });
关键修改点
- 移除第二个
beforeEach中的fixture.detectChanges():避免组件提前初始化并订阅初始的Observable。 - 在每个测试用例中,先设置
alert$再调用fixture.detectChanges():确保组件订阅的是当前测试用例需要的Observable,这样新的发射值才能被组件接收到。
这样调整后,每个测试用例都会独立初始化组件,订阅对应测试场景的alert$ Observable,就能正确验证不同的异常数据场景了。
内容的提问来源于stack exchange,提问作者Zach Starnes
相关产品推荐
相关产品推荐

