Kevin Beal
Kevin Beal

Reputation: 10858

fixture.whenStable() never resolves when testing my component

How do I debug the testing situation where await fixture.whenStable() never resolves.

Put another way: what does this indicate about my component?

I have a spec that looks like this:

it('should open the modal when ?series is navigated to', async () => {
    const { instance, fixture } = await _render();
    const openSpy = spyOn(instance, 'openModal');
    expect(openSpy).not.toHaveBeenCalled();
    _updateQuery({ series: '' });
    fixture.detectChanges();
    // await fixture.whenStable(); // <- never resolves
    expect(openSpy).toHaveBeenCalled(); // <- fails
});

(I need to use an actual async function to work with my testing library.)

Calling await fixture.whenStable() hangs no matter where in the function body I put it – seems to indicate that the issue is present right when the component is loaded.

I don't really understand what whenStable() even does. Perhaps if I did I could figure this out without help.

Upvotes: 2

Views: 8960

Answers (2)

Kevin Beal
Kevin Beal

Reputation: 10858

In my particular case, the issue was that I assumed the lifecycle hook ngAfterViewchecked only fired once, but actually fires repeatedly.

I was assigning a value to a field on my component inside of that handler, so the value of that field kept changing (because it needed to be calculated).

Because the value of the field changed things in the view, and that triggers ngAfterViewChecked, and the JavaScript engine's task queue was being added to thereby, the state of the component could not become "stable".

@Component({ ... })
export class MyComponent {
    listToRepeatOver = [];

    ngAfterViewChecked() {
        this.listToRepeatOver = getList();
    }
}

I suspected something was up because I saw that purple flashing effect in the Elements panel of the Chrome devtools that indicates that DOM changed on an element inside my component template. (It was happening in rapid-fire succession).

Lesson being: If you make an update inside of a lifecycle hook which causes that lifecycle hook to fire again, then you'll get this result.

Upvotes: 2

Eluzive
Eluzive

Reputation: 99

According to https://angular.io/guide/testing

The fixture.whenStable() returns a promise that resolves when the JavaScript engine's task queue becomes empty.

So in this case:

class AppComponent implements OnInit {

  someText: Observable<string>;

  ngOnInit() {
    this.someText = of('some string');
  }

}

whenStable resolves when observable in ngOnInit emits it's value.

it('after onInit observable should be set', async(async () => {
  component.ngOnInit();
  await fixture.whenStable();
  fixture.detectChanges();
  console.log('after whenStable'); // <- now you gonna see this in console
}));

Upvotes: 1

Related Questions