计时器启动后,如何正确调用函数的单元测试. Angular 7,RXJS 6 [英] How to unit test a function is called correctly after a timer has fired. Angular 7, RXJS 6

查看:63
本文介绍了计时器启动后,如何正确调用函数的单元测试. Angular 7,RXJS 6的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Angular 7应用程序中,我有一个用于跟踪活动用户任务的服务.在该服务中,计时器每秒钟运行一次,以检查是否在30秒内仍未完成任何任务.如果发现任何任务已过期,则通过服务上的事件发射器发射该任务,以在其他地方进行处理.当应用程序在浏览器中运行时,这一切都有效,但是当我尝试编写单元测试以测试fakeAsync环境中的行为时,tick(X)不会延长时间(或fakeAsync不会模拟时间服务中为使tick()正常运行而创建的任何新Date()" ).

In my Angular 7 application, I have a service that is used to track active user tasks. In the service, a timer runs every second to check if any tasks still haven't been completed within 30 seconds. If any tasks are found to have expired, the task is emitted via an event emitter on the service to be handled elsewhere. This all works when the app is running in a browser, but when I try to write a unit test to test the behavior in a fakeAsync environment, tick(X) does not advance the time (or fakeAsync is not mocking the time for any 'new Date()' created within the service for tick() to work properly).

由于我是角单元测试的新手,所以我也承认问题可能出在我如何设置测试上(实际上,我怀疑这是问题所在).

As I am new to angular unit testing, I also admit the issue could be how I am setting up the tests (in fact, I suspect this is the issue).

我发现许多有关Angular较早版本的帖子都存在日期无法正确模拟的问题,因此建议的解决方法是使用asyncScheduler绕过tick或导入其他npm软件包,或者,我什至尝试了其他版本的该区域的功能.我尝试了这些但没有成功.我还通过运行下面通过的简单测试来测试了@angular/core/testing中的fakeAsync()tick()函数:

I have found a number of posts regarding older versions of Angular have had issues with Date not being mocked properly so the suggested workarounds were to use asyncScheduler to bypass tick or import other npm packages or, I have even tried other versions of the functions from the zone. I have tried these with no success. I have also tested the fakeAsync() and tick() functions from @angular/core/testing by running the simple test below which passes:

  it('should tick', fakeAsync(() => {
    const firstDate = new Date();
    tick(30000);
    const secondDate = new Date();
    expect(secondDate.getTime() - firstDate.getTime()).toBe(30000);
  }));

这是该服务的简化版本:

Here is a simplified version of the service:

export class UserTaskTrackerService {
  TaskExpired = new EventEmitter<UserTask>

  private activeUserTasks: UserTask[] = []
  private oneSecondTimer;
  private timerSubscription$;

  constructor() {
    this.oneSecondTimer = timer(1000, 1000);
    this.timerSubscription$ = this.oneSecondTimer.subscribe(() => {
       this.checkForExpiredUserTasks();
    });
  }

  addNewTask(task: UserTask) {
    if(this.taskExists(task)) {
      this.completeTask(task);  // not included in example
    }
    else {
      task.startTime = new Date();
      this.activeUserTasks.push(task);
    }
  }

  private checkForExpiredUserTasks() {
    const currentTime = new Date();
    const expiredTasks: UserTask[] = [];

    this.activeUserTasks.forEach(userTask => {
       if (this.taskHasExpired(userTask.startTime, currentTime)) {
         expiredTasks.push(userTask);
       }
    });

    if (expiredTasks.length > 0) {
       this.handleExpiredTasks(expiredTasks);
    }
  }

  private taskHasExpired(taskStartTime: Date, currentTime: Date): boolean {
     return (currentTime.getTime() - taskStartTime.getTime()) / 1000 > 30;
  }

  private handleExpiredTasks(expiredTasks: UserTasks[]) {
     // remove task(s) from collection and then emit the task
  }
}

单元测试示例.在此示例中,来自@ angular/core/testing

Example unit tests. In this example, all testing functions from from @angular/core/testing

describe('User Action Tracking Service', () => {
   let service: UserTaskTrackerService;
   let testBed: TestBed;

   beforeEach(() => {
     TestBed.configureTestingModule({
         providers: [UserTaskTrackerService]
    });
   });

   beforeEach(() => {
     service = TestBed.get(UserTaskTrackerService);
   });

   it('should tick', fakeAsync(() => {
      const firstDate = new Date();
      tick(30000);
      const secondDate = new Date();
      expect(secondDate.getTime() - firstDate.getTime()).toBe(30000);
    }));

// Other tests removed for brevity

  it(`it should emit a UserTask when one expires`, fakeAsync(() => {
    let expiredUserTask: UserTask;

    service.TaskExpired.subscribe((userTask: UserTask) => {
      expiredUserTask = userTask;
    });

    service.addNewTask(new UserTask('abc', 'test action - request'));
    expect(service.getTaskCount()).toBe(1);

    tick(31000);

    expect(expiredUserTask).toBeDefined();
    expect(expiredUserTask.id).toBe('abc');
  }));
});

运行测试时,我得到一个失败的结果,提示预期'未定义'为'已定义'."

When the test runs, I get a failed result saying "expected 'undefined' to be 'defined'. "

如果继续观察控制台,在测试完成后约30秒,我会看到一些console.log输出,该输出在我的服务代码中,当找到过期任务时,该输出会打印过期用户任务.

If I continue to watch the console, ~30 seconds after the testing finished, I see some console.log output which I have in my service code which prints the expired user task when an expired task is found.

推荐答案

我找到了答案,我想这是有道理的.

I have found the answer and I guess it makes sense.

TL:DR =>在服务(或组件)中使用timer()或setInterval()时,需要在fakeAsync函数中创建服务(或组件),以便正确修补不同的日期/时间函数使tick()函数起作用.使用在fakeAsync()外部创建的服务或组件的副本将不起作用.当在service/组件中使用timers/setInterval时,您还需要在测试完成后暴露一个函数以处置计时器,否则您将收到错误消息:

TL:DR => When using timer() or setInterval() within a service (or component), the service (or component) needs to be created within the fakeAsync function in order to correctly patch the different date/time functions for the tick() function to work. Using a copy of the service or component created outside of fakeAsync() will not work. When using timers / setInterval within a service/ component, you will also need to have a function exposed to dispose of the timer after the test has finished or else you will get the error message:

错误:队列中还有1个定期计时器.

Error: 1 periodic timer(s) still in the queue.

对于那些仍在阅读的人,这就是我进行测试的方式.

For those still reading, this is how I got the test working.

向该服务添加一个"disposeTimers()"函数.

Add a 'disposeTimers()' function to the service.

disposeTimers() {
    if (this.timerSubscription$) {
        if (!this.timerSubscription$.closed) {
            this.timerSubscription$.unsubscribe();
            this.oneSecondTimer = undefined;
        }
    }
}

然后,为了进行测试,我使用了以下代码:

Then for my test, I used the following code:

   it(`it should emit a UserTask when one expires`, fakeAsync(() => {
      let expiredUserTask: UserTask;
      const singleTestService = new UserTaskTrackerService();

      singleTestService.TaskExpired.subscribe((userTask: UserTask) => {
      expiredUserTask = userTask;
      });

      singleTestService.addNewTask(new UserTask('abc', 'test action - request'));
      expect(singleTestService.getTaskCount()).toBe(1);

      tick(31000);

      expect(expiredUserTask).toBeDefined();
      expect(expiredUserTask.id).toBe('abc');
      singleTestService.disposeTimers();
   }));

我尝试通过使用"beforeEach(fakeAsync()=> {...});来减少黑客行为."生成服务,但这会导致"1个定期计时器仍在队列中".即使您丢弃了计时器,每次测试也会出现错误.

I tried making it less hacky by using " beforeEach( fakeAsync() => { ... });" to generate the service but this causes the "1 periodic timer(s) still in the queue." error for every test, even if you dispose of the timers.

这篇关于计时器启动后,如何正确调用函数的单元测试. Angular 7,RXJS 6的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆