测试异步代码¶
测试准备¶
将AuthService.isAuthenticated修改为返回一个Promise.
1 2 3 4 5 | export class AuthService { isAuthenticated(): Promise<boolean> { return Promise.resolve(!!localStorage.getItem('token')); } } |
LoginComponent相应变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 | export class LoginComponent implements OnInit { needsLogin: boolean = true; constructor(private auth: AuthService) { } ngOnInit() { this.auth.isAuthenticated().then((authenticated) => { this.needsLogin = !authenticated; }) } } |
anthenticated值通过异步返回。
不处理异步¶
1 2 3 4 5 6 7 8 | it('Button label via jasmine.done', () => { fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Login'); spyOn(authService, 'isAuthenticated').and.returnValue(Promise.resolve(true)); component.ngOnInit(); fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Logout'); }); |
- 注意到测试代码中手动调用了生命周期的回调方法ngOnInit,测试环境下Angular是不会执行生命周期回调的。
- 上面的测试用例将不会通过,原因是最后一个expect执行时,AuthService.isAuthenticated()方法并未解析出最终结果值。
解决办法¶
Jasmines done 方法¶
Jasmine 内置有处理异步代码的方式:done.
1 2 3 4 5 6 7 8 9 10 11 | it('Button label via jasmine.done', (done) => { fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Login'); let spy = spyOn(authService, 'isAuthenticated').and.returnValue(Promise.resolve(true)); component.ngOnInit(); spy.calls.mostRecent().returnValue.then(() => { fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Logout'); done(); }); }); |
- spec方法传入done参数
- 通过spy的回调函数执行变化检测、断言判断
- 最后执行done方法告知jasmine框架
async 与 whenStable¶
Angular 提供了另外2种方法来测试异步代码: async,whenStable.
新的方式代码如下:
1 2 3 4 5 6 7 8 9 10 | it('Button label via async() and whenStable()', async(() => { fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Login'); spyOn(authService, 'isAuthenticated').and.returnValue(Promise.resolve(true)); fixture.whenStable().then(() => { fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Logout'); }); component.ngOnInit(); })); |
- 将spec方法用async包装
- 将变化检测及断言判断代码放在whenStable().then内部执行
async方法内的代码在特定的aysnc测试zone中执行。这个zone会拦截并跟踪所有的promises。只有当所有的promise解析后,才会执行whenStable的promise(then方法体)
fakeAsync 与 tick¶
1 2 3 4 5 6 7 8 9 10 11 | it('Button label via fakeAsync() and tick()', fakeAsync(() => { expect(el.nativeElement.textContent.trim()).toBe(''); fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Login'); spyOn(authService, 'isAuthenticated').and.returnValue(Promise.resolve(true)); component.ngOnInit(); tick(); fixture.detectChanges(); expect(el.nativeElement.textContent.trim()).toBe('Logout'); })); |
- 与async 类似,这次用 fakeAsync包裹测试代码.
- 调用tick() 等待所有异步活动完成.
与async类似, fakeAsync方法的内部代码也在特定的test zone中执行,拦截并跟踪所有promise。tick方法将阻止当前线程知道所有异步活动都完成。
相比async,这种方式的测试代码可读性好,容易理解,看似是执行同步代码一样。
需要注意的是,fakeAsync不能跟踪XHR 请求。