jeudi 1 octobre 2020

Angular Testing - Tick doesn't work if timer is in component initialization

Question

How do I get tick to work, or at least, how do I get the test to advance 10s to appropriately call submit in my component?

Note: I don't want to do await new Promise(r => setTimeout(r, 10000)) because it will make my tests run long, and tests are supposed to be short

Goal

I want submit to only call cb after 10 seconds has elapsed from the creation of the component

Description

I have a timer in my component that completes after 10s. This timer changes a subject from false to true, and is used to determine whether or not it is considered valid to submit the data in the component.

In testing, tick does not seem to advance the timer at all, and it actually runs a full 10s. I attempted to fix this by putting a fakeAsync in the beforeEach that creates the component to no avail.

What I have tried

  • Using fakeAsync in the test component init, as well as the test
  • Using fakeAsync in only the test
  • Using setTimeout(() => this.obs.next(true), 10_000) instead of timer
  • Using empty().pipe(delay(10000)).subscribe(() => this.obs.next(true)); instead of timer
  • Putting the timer in ngOnInit instead of constructor
  • Putting the timer in constructor instead of ngOnInit

Observations

If you adjust this code

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

to instead be this

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

You will find that every time a test runs, the Javascript debugger in Dev Tools is triggered 10 seconds after component creation (instead of instantly if tick worked).

Code

Here's the code. At the bottom is a link to a minimal reproduction on 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();
  }));
});

Minimal Reproduction Repo

Link To Github

Aucun commentaire:

Enregistrer un commentaire