首页 > 解决方案 > Angular 测试 - 如果计时器在组件初始化中,Tick 不起作用

问题描述

问题

我如何开始tick工作,或者至少,如何让测试提前 10 秒以适当地调用submit我的组件?

注意:我不想这样做,await new Promise(r => setTimeout(r, 10000))因为它会使我的测试运行很长时间,而测试应该很短

目标

我只想在创建组件后 10 秒后submit调用cb

描述

我的组件中有一个计时器,它在 10 秒后完成。此计时器将主题从假更改为真,并用于确定提交组件中的数据是否被认为是有效的。

在测试中,tick似乎根本没有提前定时器,它实际上运行了整整 10 秒。我试图通过fakeAsyncbeforeEach创建组件的中添加一个无济于事来解决这个问题。

我试过的

观察

如果您调整此代码

    timer(10_000).subscribe(() => this.testThis$.next(true));

改为这样

    timer(10_000).subscribe(() => {
      debugger;
      this.testThis$.next(true)
    });

您会发现每次运行测试时,Dev Tools 中的 Javascript 调试器都会在组件创建后 10 秒触发(而不是在 tick 有效时立即触发)。

代码

这是代码。底部是 GitHub 上最小复制的链接。

// component code
import { Component, OnInit, Inject } from '@angular/core';
import { BehaviorSubject, Subject, timer } from 'rxjs';
import { first, filter } from 'rxjs/operators';

@Component({
  selector: 'app-tick-test',
  templateUrl: './tick-test.component.html',
  styleUrls: ['./tick-test.component.scss']
})
export class TickTestComponent implements OnInit {

  public testThis$: Subject<boolean>;

  constructor(
    @Inject('TICK_CALLBACK') private readonly cb: () => void,
  ) {
    this.testThis$ = new BehaviorSubject<boolean>(false);
    timer(10_000).subscribe(() => this.testThis$.next(true));
  }

  public ngOnInit(): void {
  }

  public submit(): void {
    // call the callback after 10s
    this.testThis$
      .pipe(first(), filter(a => !!a))
      .subscribe(() => this.cb());
  }

}
// test code
/**
 * The problem in this one is that I am expecting `tick` to advance the
 * time for the timer that was created in the constructor, but it is not working
 */



import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';

import { TickTestComponent } from './tick-test.component';

describe('TickTestComponent', () => {
  let component: TickTestComponent;
  let fixture: ComponentFixture<TickTestComponent>;

  let callback: jasmine.Spy;

  beforeEach(async(() => {

    callback = jasmine.createSpy('TICK_CALLBACK');

    TestBed.configureTestingModule({
      providers: [
        { provide: 'TICK_CALLBACK', useValue: callback },
      ],
      declarations: [ TickTestComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TickTestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should be true after 10s', fakeAsync(() => {
    tick(10_001);

    component.submit();
    expect(callback).toHaveBeenCalled();
  }));
});

最小复制回购

链接到 Github

标签: angulartypescripttestingtimer

解决方案


解决方案

  1. 进入fixture.detectChanges()每个测试,然后打电话给tick(10_000)那里。
  2. 移动timer(10_000)...ngOnInit组件中

发生了什么事

每当您使用fakeAsync您的代码可以在其中运行的“区域”时。根据我的观察,这个区域“存在”直到超出范围。通过fakeAsync在 中使用beforeEach,您会破坏该区域,并且您会遇到计时器未完成的问题(尽管计时器未完成是理想的结果)。

您想将其timer移入,ngOnInit因为它不会在调用时立即.createComponent调用。而是fixture.detectChanges()在您第一次运行时调用它。因此,当您第一次fixture.detectChanges()在测试fakeAsync区域内调用时,ngOnInit会为您调用,计时器被捕获在区域中,您可以按预期控制时间。

代码

describe('TickTestComponent', () => {
  let component: TickTestComponent;
  let fixture: ComponentFixture<TickTestComponent>;

  let callback: jasmine.Spy;

  beforeEach(async(() => {

    callback = jasmine.createSpy('TICK_CALLBACK');

    TestBed.configureTestingModule({
      providers: [
        { provide: 'TICK_CALLBACK', useValue: callback },
      ],
      declarations: [ TickTestComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TickTestComponent);
    component = fixture.componentInstance;
    // don't run this here
    // fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should be true after 10s', fakeAsync(() => {
    // this calls ngOnInit if it is the first detectChanges call
    fixture.detectChanges();
    tick(10_001);

    component.submit();
    expect(callback).toHaveBeenCalled();
  }));
});


推荐阅读