角度测试 - 如果计时器处于组件初始化状态,则勾选不起作用

Jon*_*old 4 testing timer typescript angular

问题

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

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

目标

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

描述

我的组件中有一个计时器,它在 10 秒后完成。该定时器将主题由假变为真,用于判断组件中提交数据是否有效

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

我尝试过的

  • 在测试组件init中使用fakeAsync,以及测试
  • fakeAsync仅在测试中使用
  • 使用setTimeout(() => this.obs.next(true), 10_000)而不是计时器
  • 使用empty().pipe(delay(10000)).subscribe(() => this.obs.next(true));而不是计时器
  • 放入timer代替ngOnInit构造函数
  • timerin 构造函数而不是ngOnInit

观察结果

如果你调整这段代码

    timer(10_000).subscribe(() => this.testThis$.next(true));
Run Code Online (Sandbox Code Playgroud)

改为这样

    timer(10_000).subscribe(() => {
      debugger;
      this.testThis$.next(true)
    });
Run Code Online (Sandbox Code Playgroud)

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

代码

这是代码。底部是 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());
  }

}
Run Code Online (Sandbox Code Playgroud)
// 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();
  }));
});

Run Code Online (Sandbox Code Playgroud)

最小复制回购

链接到 Github

Jon*_*old 5

解决方案

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

发生了什么事

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

您希望将 移入timerngOnInit因为它不会在调用时立即调用.createComponent相反,它会在您fixture.detectChanges()第一次运行时被调用。因此,当您第一次fixture.detectChanges()在测试区域内调用时,系统会为您调用,计时器将在区域中捕获,您可以按预期控制时间。fakeAsyncngOnInit

代码

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();
  }));
});

Run Code Online (Sandbox Code Playgroud)