Question
How can I make the `tick` function work properly so that my test advances by 10s and calls `submit` in my component as expected?
Note: I am looking for a solution other than using
await new Promise(r => setTimeout(r, 10000))
to avoid having lengthy tests.
Goal
The objective is to ensure that `submit` only triggers `cb` after 10 seconds have passed since the component was created.
Description
Within the component, there is a timer set for 10s. Once this timer elapses, it changes a subject from false to true, indicating whether it is "valid" to submit data within the component.
In testing, the `tick` function does not appear to progress the timer, leading to the full 10s duration. Attempts were made to use `fakeAsync` in the `beforeEach` section when creating the component but without success.
What I have tried
- Using `fakeAsync` in both the test component initialization and during the actual test
- Using `setTimeout(() => this.obs.next(true), 10_000)` instead of the timer
- Replacing the timer with `empty().pipe(delay(10000)).subscribe(() => this.obs.next(true));`
- Moving the `timer` logic from `ngOnInit` to the constructor and vice versa
Observations
An adjustment in the code:
timer(10_000).subscribe(() => {
debugger;
this.testThis$.next(true)
});
reveals that upon running a test, the JavaScript debugger in Dev Tools activates 10 seconds after the component creation rather than immediately if `tick` functionality worked correctly.
Code
Shown below are the relevant snippets of code. A link to a minimal reproduction on GitHub can be found at the end.
// 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 {
// Execute the callback after 10s
this.testThis$
.pipe(first(), filter(a => !!a))
.subscribe(() => this.cb());
}
}
// Test Code
/**
* The issue here revolves around expecting `tick` to advance
* the timer initiated in the constructor, which is currently not functioning
*/
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 become true after 10s', fakeAsync(() => {
tick(10_001);
component.submit();
expect(callback).toHaveBeenCalled();
}));
});