在上篇文章中我们讲述了如何在angular中的使用弹珠测试来模拟http异步请求。
使用弹珠测试的方法虽然很官方很正统,但由于其调度器的生效周期的设置原因,若要在单元测试中结合fixture.detectChanges();
在某个测试用例完毕后继续与组件进行交互,则会发生No test scheduler initialized!
错语。
在单元测试中手动地与组件进行交互虽不正规,但该方案无疑会让新成员感受到单元测试友好的一面,降低对单元测试的心理排斥。
sample
比如当前有一组件的功能是当用户在input中输入相应的内容时,首先使用500ms防抖阻止冗余的请求,接着进行后台的模拟异步请求:
组件相关代码:
// 当输入发生变化时,500ms稳定后重新由后台拉取数据
this.input.valueChanges.pipe(
debounceTime(500),
distinctUntilChanged(),
switchMap(value => {
this.tagName = value;
// 请求后台
return this.tagService.findTop20ByCourseAndNameLike(this.state.courseId, value);
})
).subscribe((tags: Array<Tag>) => {
this.tags = tags;
});
使用弹珠测试的服务层tagService相关代码:
/**
* 模拟真实后台,异步返回数据
*/
public findTop20ByCourseAndNameLike(courseId: number, name: string): Observable<Array<Tag>> {
Assert.notNull(courseId, '课程ID未定义');
const tags = new Array<Tag>();
for (let i = 0; i < 20; i++) {
tags.push(new Tag({
id: randomNumber(),
name: randomString('name'),
course: new Course({id: randomNumber()})
}));
}
// 使用弹珠测试返回tags
return cold('---(x|)', {x: tags});
}
基于上述代码,在单元测试中若仅把单元测试的目标定格为单元测试,则不会有任何问题。
但如果我们将单元测试的目标扩展为:不启动后台的前提下,单独快速开发前台相关组件。那么弹珠测试将无法满足该需求:
当我们看到单元测试生成的被测试组件时,总想点击一些功能性的按钮,用“眼睛”来确认自己的程序是正确的 ---- 尽管我们知道这并不是单元测试的正确用法,但却无法控制住自己的好奇心。
fit('should create', fakeAsync(() => {
expect(component).toBeTruthy();
// 自动检测组件变化并重新组件渲染
fixture.autoDetectChanges();
}));
基于弹珠测试在与被测试组件进行人为交互则会发生错误:
如上所示,我们本期待输入某些字符后,cold()
方法能够在一定时间后返回模拟后台返回的数据,但却得到了No test scheduler initialized!
的错误。
个人猜想这可能是由于jasmine-marbles设置了有效上下文作用域造成的。
rxjs-marbles
rxjs-marbles
提供了一种解决方法:让我们可以在不使用补丁的情况下,使用tick()
方法来模拟普通时钟以及RxJS调度器的时间推进。
这使得我们可以抛弃弹珠测试而改用delay
的方法来完成异步模拟请求,更重要的:像debounceTime
等一些时间相关的操作符也能够被轻松的模拟。
从而达到了:1. 在单元测试中以够实现模拟时间推进,从而保障单元测试的正常运行。2. 单元测试结束后delay()
、debounceTime
等操作符并不依赖于测试环境,从而保障了在人为与组件进行交互时,仍然能够接收到模拟后台的异步数据。
安装
npm i rxjs-marbles --save-dev
使用
tagService
// 删除原弹珠测试
- return cold('---(x|)', {x: tags});
// 使用of + delay的方法来模拟异步延迟请求
+ return of(tags).pipe(delay(500));
单元测试验证
使用tick进行模拟时钟推进:
import {fakeSchedulers} from 'rxjs-marbles/jasmine/angular';
// fakeSchedulers使我们可以使用tick()来模拟rxjs中的时钟推进
it('input change', fakeSchedulers(() => {
component.tags = [];
component.input.setValue('hello');
// 模拟推进第一个防抖
tick(500);
// 断言正确的推进了防抖操作符
expect(component.tagName).toEqual('hello');
// 断言由于异步delay的作用,组件中的tags未发生变化
expect(component.tags.length).toEqual(0);
// 模拟推进tagService中的delay
tick(500);
// 断言成功推进了delay操作符
expect(component.tags.length).toBeGreaterThan(1);
}));
组件交互效果
此时,我们借助此单元测试生成的组件进行一些交互性的测试
总结
弹珠测试仅适用于单元测试的测试环境,一旦单元测试结束弹珠的相关代码便会失效。从而使得无法在单元测试中借助弹珠测试对组件进行手动的查看。在初次接触单元测试时,无法更加直观的感受到组件相关功能。rxjs-marbles
使tick()
方法能够作用于RxJS
的相关时间
操作符上。当单元测试结束后,被测组件仍然可以在相关时间操作符的支持下完成交互功能,这使我们能够在单元测试中更加直观的来观察组件的动作。
本文作者:河北工业大业梦云智开发团队 潘杰
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。