2017-07-15 2 views
1

Ich schreibe Jasmine/Karma/Webpack Komponententests für eine Anwendung mit vielen internen Versprechungen, die tief im Code gelöst werden. Ich würde gerne angular async, fixture.detectChanges und fixture.whenStable verwenden.eckig 4.3.0 rufende fixture.whenStable nach dem Ausführen einer asynchronen Funktion

Als Beweis des Konzepts habe ich die folgende einfache aber sehr asynchrone Komponente gemacht.

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

Um dies zu testen, erstelle ich die Komponente in einem asynchronen Block. Das funktioniert, und der it-Block wird gestartet, nachdem das Versprechen des Konstruktors aufgelöst wurde.

Innerhalb des Tests rufe ich comp.method2() auf, was bewirkt, dass ein Versprechen nach 2 Sekunden aufgelöst wird. Allerdings .... und hier ist der Teil, den ich nicht bekomme ... Aufruf von fixture.isStable() unmittelbar nach dem Aufruf von comp.method2() gibt true zurück. Ich hätte falsch erwartet. Schlimmer .... fitter.whenStable() wird sofort aufgelöst. Da das Versprechen in der tatsächlichen Methode nicht gelöst hat, habe ich noch nicht den Wert, den ich testen möchte.

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


}); 

Die Konsolenprotokollausgabe ist unten. Ich war

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

erwartet bekam aber

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 

Ohne explizite Kenntnis von comp.method2() 's Einbauten, wie kann ich Winkel warten, bis alle Versprechungen von Methoden lösen in der it() Methode aufgerufen? Ich dachte, das wäre die explizite Rolle von fixture.whenStable.

Antwort

0

Beantworten meiner eigenen Frage. Na sicher! Ich bin nicht in einer Zone gelaufen. Es gibt also zwei Möglichkeiten, das zu beheben. (Ich habe beides bewiesen).

Lösung 1: Holen Sie sich das native Element und klicken Sie darauf ..... wenn Sie ein Browserereignis testen. (So ​​habe ich

<button id='unit-test-method-2-button'(click)="method2()">Method 2</button> 

und dann genannt method2() wurde

const button = de.query(By.css('#unit-test-method-2-button')); 
expect(button).toBeDefined(); 
button.nativeElement.click(); 

Eine noch bessere Lösung für meine Bedürfnisse mit einfach NgZone zu injizieren. Das erlaubte mir, verschachtelte Anrufe fixture.whenStable zu tun ().

So habe ich

beforeEach(inject([NgZone], (injectedNgZone: NgZone) => { 
    ngZone = injectedNgZone; 
})); 

Und das mir erlaubt, asynchronen method2 zu nennen, ein d dann asynchrone method3, und verwenden Sie fixture.whenStable ...

it('should create the component', async(() => { 
    ngZone.run(() => { 
    console.log('time it.start', Date.now() - startTime, comp); 
    expect(comp).toBeDefined(); 
    expect(fixture.isStable()).toBe(true, 'expect fixture to be stable'); 
    comp.method2(); 
    expect(fixture.isStable()).toBe(false, 'expect fixture not to be stable'); 
    fixture.whenStable() 
     .then(data => { 
     fixture.detectChanges(); 
     expect(comp.v2).toBe('value2'); 
     comp.method3(); 
     fixture.whenStable() 
      .then(data => { 
      fixture.detectChanges(); 
      expect(comp.v3).toBe('value3'); 
      }); 
     }); 
    }); 
})); 
Verwandte Themen