Модульное тестирование Angular 5: как правильно настроить обнаружение изменений?

Как сделать так, чтобы обнаружение изменений в модульных тестах работало правильно? Из исходников changeDetection следует запускать после того, как микрозадачи опустеют (включая событийные задачи?).

this._onMicrotaskEmptySubscription = ngZone.onMicrotaskEmpty.subscribe({
    next: () => {
        if (this._autoDetect) {
            // Do a change detection run with checkNoChanges set to true to check
            // there are no changes on the second run.
            this.detectChanges(true);
        }
    }
});

В этом коротком примере обнаружение изменений запускается после setTimeout, но не после нажатия на элемент вручную. Есть ли правильный способ инициировать обнаружение изменений после отправки события (внутри зоны fakeAsync, без приспособления.detectChanges, потому что в этом случае обнаружение изменений не будет таким же, как в реальной жизни)?

import {
    fakeAsync, tick, ComponentFixture, TestBed, ComponentFixtureAutoDetect
} from '@angular/core/testing';
import {
    Component, QueryList, ViewChildren
} from '@angular/core';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
describe('bug', () => {
    let host: Host;
    let fixture: ComponentFixture<Host>;
    @Component({
        selector: 'child',
        template: ``,
    })
    class Child {}
    @Component({
        template: `
            <ng-container *ngFor="let show of shows">
                <child *ngIf="show"></child>
            </ng-container>
            <button (click)="shows[1] = true">show</button>`
    })
    class Host {
        shows = [false, false];
        @ViewChildren(Child) children: QueryList<Child>;
        constructor() {
            setTimeout(() => this.shows[0] = true, 50);
        }
    }
    fit('test', fakeAsync(() => {
        TestBed.configureTestingModule({
            imports: [
                CommonModule,
            ],
            declarations: [
                Host, Child,
            ],
            providers: [{
                provide: ComponentFixtureAutoDetect,
                useValue: true,
            }]
        });
        fixture = TestBed.createComponent(Host);
        host = fixture.componentInstance;
        tick(10);
        expect(host.children.length).toEqual(0);
        tick(50);
        expect(host.children.length).toEqual(1);
        const button = fixture.debugElement.query(By.css('button'));
        button.triggerEventHandler('click', new Event('click'));
        tick(50);
        // fixture.detectChanges();
        expect(host.children.length).toEqual(2); // fails here
    }));
});

person Ilia Volk    schedule 04.03.2018    source источник


Ответы (1)


Я нашел решение своего вопроса. HTMLElement.dispatchEvent следует использовать. Как вызвать события input, keyup:

const inputDe = this.de.query(By.css('input'));
const inputEl = inputDe.nativeElement;
inputEl.value = text;
inputEl.focus(); // if it has matAutocompleteTrigger value accessor
inputEl.dispatchEvent(new Event('input'));
inputEl.dispatchEvent(new KeyboardEvent('keyup', {
      key: 'Enter',
}));

Полный пример

import {
    fakeAsync, tick, ComponentFixture, TestBed, ComponentFixtureAutoDetect
} from '@angular/core/testing';
import {
    Component, QueryList, ViewChildren
} from '@angular/core';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
fdescribe('bug', () => {
    let host: Host;
    let fixture: ComponentFixture<Host>;
    @Component({
        selector: 'child',
        template: ``,
    })
    class Child {}
    @Component({
        template: `
            <ng-container *ngFor="let show of shows">
                <child *ngIf="show"></child>
            </ng-container>
            <button (click)="shows[1] = true">show</button>`
    })
    class Host {
        shows = [false, false];
        @ViewChildren(Child) children: QueryList<Child>;
        constructor() {
            setTimeout(() => this.shows[0] = true, 50);
        }
    }
    it('test', fakeAsync(() => {
        TestBed.configureTestingModule({
            imports: [
                CommonModule,
            ],
            declarations: [
                Host, Child,
            ],
            providers: [{
                provide: ComponentFixtureAutoDetect,
                useValue: true,
            }]
        });
        fixture = TestBed.createComponent(Host);
        host = fixture.componentInstance;
        tick(10);
        expect(host.children.length).toEqual(0);
        tick(50);
        expect(host.children.length).toEqual(1);
        const button = fixture.debugElement.query(By.css('button'));
        button.nativeElement.dispatchEvent(new Event('click')); // proper way
        tick(50);
        expect(host.children.length).toEqual(2); // no fail now
    }));
});
person Ilia Volk    schedule 05.03.2018