【问题标题】:Angular Testing - Tick doesn't work if timer is in component initializationAngular 测试 - 如果计时器在组件初始化中,Tick 不起作用
【发布时间】:2020-10-01 17:51:58
【问题描述】:

问题

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

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

目标

我希望 submit 仅在创建组件后 10 秒后调用 cb

说明

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

在测试中,tick 似乎根本没有提前计时器,它实际上运行了整整 10 秒。我试图通过在 beforeEach 中添加 fakeAsync 来解决此问题,但该组件无济于事。

我尝试过的

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

观察

如果你调整这个代码

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

最小复制回购

Link To Github

【问题讨论】:

    标签: angular typescript testing timer


    【解决方案1】:

    解决方案

    1. fixture.detectChanges() 移动到每个测试中,并在那里调用tick(10_000)
    2. timer(10_000)...移动到组件中的ngOnInit

    发生了什么

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

    您想将timer 移动到ngOnInit,因为它不会在.createComponent 调用时立即调用。当您第一次运行fixture.detectChanges() 时,它而不是 被调用。因此,当您第一次在测试的fakeAsync 区域内调用fixture.detectChanges() 时,会为您调用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();
      }));
    });
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多