随着对angular利用学习的深刻,如何在单元测试中模仿http申请提早便提上了日程。在没有http申请提早以前,单元测试中咱们都是应用of()
来手动发送数据的。of()
办法在单元测试中无疑带来了微小的便利性,但因为同步
的机制,使其未能齐全的模仿中在生成环境中http申请提早可能对组件带来的冲击,所以在启用of()
进行单元测试后无奈保障该组件在生产环境中是100%牢靠运行的。
浏览本文须要对angular单元测试有肯定理解。
sample
简略举个of()
的例子来查看其如何成为异步申请单元测试中的不牢靠元素。
V层显示学生的姓名
<h1>{{student.name}}</h1>
C层初始化学生的值为undefined
/** * 该值初始化为undefined */ student: {name: string}; ngOnInit() { // 调用服务来获取ID为1的学生 this.studentService.getById(1) .subscribe(student => { console.log('1接管到了订阅的数据'); this.student = student; }) console.log('2ngOnInit执行结束,开始渲染V层');
用于单元测试的测试桩StudentStubService
getById(id: number): Observable<{name: string}> { const student = {name: 'hebut yunzhi'}; // 应用of()办法来返回可观察者,该观察者在被订阅时将同步发送数据 return of(student); }
控制台打印后果如下:
1接管到了订阅的数据2ngOnInit执行结束,开始渲染V层
对应的程序执行流程如下:
生产环境
而生产环境中因为进行真正的http申请,该申请是异步的且必然有提早,所以实在的控制台状况如下:
2ngOnInit执行结束,开始渲染V层1接管到了订阅的数据
就便成了这样:
也就是说:在有异步申请的状况下,此单元测试并没能保障该组件在生产环境中的正确运行。
接下来,本文将提供两种模仿http申请提早的办法。
没有service的DEMO:[https://stackblitz.com/edit/a...](https://stackblitz.com/edit/a...
delay操作符
RxJS
提供了delay
操作符来提早异步发送数据,所以模仿http申请提早的最简略的办法便是在of()
办法的根底上退出delay
操作符。比方咱们将后面的测试桩修改为:
getById(id: number): Observable<{name: string}> { const student = {name: 'hebut yunzhi'}; // 提早500MS异步发送数据 return of(student).delay(500); }
此时便起到了提早500MS异步发送数据的目标,所以在单元测试中执行相应的测试代码执行流程如下:
如上图,在单元测试中产生了异样。此异样揭示咱们组件在初始化的过程中,没有对student进行正确的初始化。为了避免生产环境中产生异样的谬误,特对组件修改如下:
/** * 初始化学生,以避免在组件初始化过程中V层渲染产生undefined异样 */ student = {} as {name: string}; ngOnInit() { this.studentService.getById(1) .subscribe(student => { // 生产环境中,以下代码将在肯定提早后被异步执行被执行。 this.student = student; })
问题
以上代码保障了组件初始化的过程未产生异样。
但因为delay
的异步执行机制,当delay
办法在500ms返回数据时,单元测试的办法曾经执行结束了且组件曾经由内存开释了。也就是说:尽管返回了数据,但因为接收数据的组件曾经不存在了,所以该数据并不能体现在被测试组件的视图中。
简略来讲就是:咱们无奈在单元测试中来查看、断言studentService.getById
的返回值是合乎预期并期可能反对组件失常工作的。
ngOnInit() { // 调用服务来获取ID为1的学生 this.studentService.getById(1) .subscribe(student => { console.log('1接管到了订阅的数据'); this.student = student; }) console.log('2ngOnInit执行结束,开始渲染V层'); it('should create', () => { console.log('3断言组件初始化胜利'); expect(component).toBeTruthy(); }); afterEach(() => { console.log('4销毁组件'); fixture.destroy(); });
执行后果:
2ngOnInit执行结束,开始渲染V层3断言组件初始化胜利4销毁组件
执行流程如下:
怎样才能保障在delay操作符500ms后发送数据时,组件并未销毁而且能够失常接管student并用接管到的学生渲染V层呢?
tick()模仿推动时钟
在angular单元测试中为咱们提供了tick()
办法来模仿时钟的推动。该办法须要配合fakeAsync
应用,比方:
it('tick test', fakeAsync(() => { let a = 1; // 500ms后,将a的值变为2 setTimeout(() => { a = 2; // ➊ }, 500); // 断言a的值未发生变化,值为1 expect(a).toEqual(1); // 应用tick模仿将时钟推动500ms,➊的代码被执行。 tick(500); // 断言a的值发生变化,值为2 expect(a).toEqual(2);}));
既然tick
的作用是模仿时钟的推动,咱们测试其是否能够影响RxJS
的delay
操作符
it('should create', fakeAsync(() => { expect(component).toBeTruthy(); // 断言因为delay操作符的起因,commpont.student的值依然初始化的值:null expect(component.student).toBeNull(); // 模仿将时钟推动500ms tick(500); // 如果tick对rxjs的delay操作符起作用,那么以下断言通过。 // 如果不起作用,那么以下断言执行失败。 expect(component.student).toBeTruthy();}));
最终的试验后果是以上代码无奈通过单元测试,即:tick函数并不对delay操作符起作用。
这实质上是因为RxJS在进行一些提早
解决的时候,并没有应用js内置的setTimeout等办法,而tick办法进行的模仿时钟推动又仅能在setTimeout等办法上失效,所以:tick办法并不可能影响RxJS的在工夫
上的解决过程。
RxJS 调度器补丁
RxJS应该是专门有一个本人的工夫调度器(scheduler),该调度器作用于一系列与工夫相干的操作符上。所以如果想在单元测试中模仿RxJS的时钟推动,则须要在提供了个假的调度器
来替换原有的真调度器
。官网把这个操作称为patch
--打补丁,具体的计划为在对应的单元测试文件中import zone.js/dist/zone-patch-rxjs-fake-async
。该文件的作用便是替换RxJS中原有的scheduler以达到能够模仿进行时钟推动的目标。
该办法可行,但打补丁
并不正统,有趣味的可参考官网文档尝试。
弹珠测试
优良平凡的RxJS为咱们提供了RxJS marble testing(弹珠测试)
以无效的在单元测试中手动控制数据的弹出。
应用marble testing
将getById
办法改写为:
marbles可能并未蕴含在angular的默认package.json中,如果是这样的话,须要手动install: npm install jasmine-marbles
getById(id: number): Observable<{name: string}> { const student = {name: 'hebut yunzhi'}; // 弹珠测试:期待3个时钟周期(-)后发送数据x,x的值为student。而后发送实现发送(|) return code('---x|', {x: student}); }
对应单元测试办法批改为:
it('should create', fakeAsync(() => { expect(component).toBeTruthy(); // 断言因为delay操作符的起因,commpont.student的值依然初始化的值:null expect(component.student).toBeNull(); // RxJS弹珠测试发送数据 getTestScheduler().flush(); // 断言student产生了变更 expect(component.student).toBeTruthy(); expect(component.student.name).toEqual(''hebut yunzhi);}));
如上所示,在单元测试中调用了getTestScheduler().flush();
来实现了弹珠测试。如此以来,上述代码高度模仿了http异步申请,高度的与生产环境相一致。单元测试无效的保障了生产环境整个我的项目的健壮性。
it('should create', fakeAsync(() => // 如下断言保障了组件在初始化的过程中未产生异样 expect(component).toBeTruthy(); // 模仿生产环境后盾异步返回数据 getTestScheduler().flush(); // 保障接管模仿数据后,组件从新渲染未产生异样 fixture.detectChanges();}));
因为上述代码并没有应用tick模仿时针推动,所以是能够移除fakeAsync办法的。
总结
在理论的生产我的项目中,有一组件在单元测试齐全OK的状况下却在线上报了undefined谬误。追踪其起因时发现是由of
办法的同步返回数据引起了。为了更好的贴近于生产我的项目,在单元测试中如何援用异步测试便摆在了眼前。
因为RxJS对工夫解决采纳了调度器的机制,所以原对setTimeout等办法起作用的tick办法并不能推动RxJS的计时器,从而使得在单元测试中应用RxJS异步测试组件的健壮性。
应用RxJS进行单元测试的正确办法为应用marble testing
,该官网提供的办法很好的解决了上述问题。
本文作者:河北工业大学梦云智开发团队 潘杰