乐趣区

angularu在单元测试中如何模拟HTTP请求延迟

随着对 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 的作用是模仿时钟的推动,咱们测试其是否能够影响 RxJSdelay操作符

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 testinggetById办法改写为:

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,该官网提供的办法很好的解决了上述问题。

本文作者:河北工业大学梦云智开发团队 潘杰

退出移动版