如何在单元测试中进行变更检测正常工作?从源代码来看,应该在微任务为空(包括事件任务?)后运行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区域内,没有fixture.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
}));
});
我找到了解决问题的方法。应该使用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
}));
});