angular 4.3.0 calling fixture.whenStable after executing an asynchronous function

Viewed 6595

I am writing jasmine/karma/webpack unit tests for an application with a lot of internal promises getting resolved deep in the code. I'd like to use angular's async, fixture.detectChanges and fixture.whenStable.

As a proof of concept, I've made the following simple but very asynchronous component.

import {Component} from "@angular/core";
import {Logger} from "../../utils/logger";

@Component({
  selector: 'unit-test.html',
  template: `    
  <div class="unit-test">
    <h3>Unit Test Component</h3>
    <h4>p1: {{p1}}</h4>
    <h4>v1: {{v1}}</h4>
    <h4>p2: {{p2}}</h4>
    <h4>v2: {{v2}}</h4>
    <h4>p3: {{p3}}</h4>
    <h4>v3: {{v3}}</h4>
  </div>`
})
export class UnitTestComponent {
  p1: Promise<string>;
  v1: string;
  p2: Promise<string>;
  v2: string;
  p3: Promise<string>;
  v3: string;

  constructor() {
    this.p1 = makeTimeoutPromise('value1', 2000);
    Logger.warn('p1 created');
    this.p1.then(data => {
      this.v1 = data
    });
  }

  method2() {
    this.p2 = makeTimeoutPromise('value2', 2000);
    this.p2.then(data => {
      this.v2 = data
    });
    Logger.warn('p2 created');
  }

  method3() {
    this.p3 = makeTimeoutPromise('value3', 2000);
    this.p3.then(data => {
      this.v3 = data
    });
    Logger.warn('p2 created');
  }
}


function makeTimeoutPromise(result: string, timeout: number) {
  return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
      resolve(result);
      Logger.warn(`resolved '${result}' after '${timeout}' seconds`);
    }, timeout)
  });
}

To test this, I create the component in an async block. This works, and the it block starts running after the constructor's promise has resolved.

Inside of the test I call comp.method2() which causes a promise to be resolved after 2 seconds. However.... and here's the part I don't get... calling fixture.isStable() immediately after calling comp.method2() returns true. I would have expected false. Worse.... fixture.whenStable() resolves immediately. Since the promise in the actual method hasn't resolved, I don't yet have the value I want to test.

import {UnitTestComponent} from './unit-test.component';
import {async, ComponentFixture, TestBed, tick} from '@angular/core/testing';
import {DebugElement} from '@angular/core';

describe('UnitTestComponent', () => {
  let de: DebugElement;
  let comp: UnitTestComponent;
  let fixture: ComponentFixture<UnitTestComponent>;
  let el: HTMLElement;
  const startTime = Date.now();
  beforeEach(async(() => {
    console.log('time beforeEach.start', Date.now() - startTime);
    const testbed = TestBed.configureTestingModule({
      declarations:[UnitTestComponent],
      imports: [],
      providers: []
    });
    // testbed.compileComponents();
    console.log('time beforeEach.initSsmpComponentLibModule', Date.now() - startTime);
    fixture = TestBed.createComponent(UnitTestComponent);
    comp = fixture.componentInstance;
    de = fixture.debugElement;
    console.log('time beforeEach.end', Date.now() - startTime);
  }));

  it('should create the component', async(() => {
    // const x = new Promise<any>((resolve, reject)=>{
    //   setTimeout(()=>{
    //     console.log('right before exit', comp);
    //     fixture.detectChanges();
    //     resolve();
    //     }, 10000);
    // });
    console.log('time it.start', Date.now() - startTime, comp);
    expect(comp).toBeDefined();
    console.log('fixture.isStable() (1)', fixture.isStable());
    comp.method2();
    console.log('fixture.isStable() (2)', fixture.isStable());
    fixture.whenStable()
      .then(data=>{
        fixture.detectChanges();
        console.log('time after whenStable(1) resolves', Date.now() - startTime, comp);
        fixture.detectChanges();
        console.log('time after whenStable(1).detect changes completes', Date.now() - startTime, comp);
        expect(comp.v2).toBe('value2');
        comp.method3();
        console.log('method3 called', Date.now() - startTime, comp);
        fixture.detectChanges();
        console.log('time after detectChanges (2)', Date.now() - startTime, comp);
        fixture.whenStable()
          .then(data=>{
            fixture.detectChanges();
            console.log('time after whenStable(3).then', Date.now() - startTime, comp);
            expect(comp.v3).toBe('value3');
          });
        console.log('time after whenStable(3)', Date.now() - startTime, comp);

      });
    console.log('time after whenStable(2)', Date.now() - startTime, comp);
  }));


});

The console log output is below. I was expecting

fixture.isStable() (2) false
time after whenStable(2) >=4386 UnitTestComponent {p1: ZoneAwarePromise, v1: "value1", p2: ZoneAwarePromise}

but got

fixture.isStable() (2) true
time after whenStable(2) 2390 UnitTestComponent {p1: ZoneAwarePromise, v1: "value1", p2: ZoneAwarePromise}

time beforeEach.start 363
time beforeEach.initSsmpComponentLibModule 363
time beforeEach.end 387
time it.start 2386 UnitTestComponent {p1: ZoneAwarePromise, v1: "value1"}
fixture.isStable() (1) true
fixture.isStable() (2) true
time after whenStable(2) 2390 UnitTestComponent {p1: ZoneAwarePromise, v1: "value1", p2: ZoneAwarePromise}
time after whenStable(1) resolves 2393 time beforeEach.start 363

Without explicit knowledge of comp.method2()'s internals, how can I have angular wait until all promises resolve from methods called inside it() method? I thought this was the explicit role of fixture.whenStable.

1 Answers
Related