【发布时间】:2019-08-07 07:24:37
【问题描述】:
第一个例子
我有以下测试:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
@Component({
template: '<ul><li *ngFor="let state of values | async">{{state}}</li></ul>'
})
export class TestComponent {
values: Promise<string[]>;
}
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
element = (<HTMLElement>fixture.nativeElement);
});
it('this test fails', async() => {
// execution
component.values = Promise.resolve(['A', 'B']);
fixture.detectChanges();
await fixture.whenStable();
// evaluation
expect(Array.from(element.querySelectorAll('li')).map(elem => elem.textContent)).toEqual(['A', 'B']);
});
it('this test works', async() => {
// execution
component.values = Promise.resolve(['A', 'B']);
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
// evaluation
expect(Array.from(element.querySelectorAll('li')).map(elem => elem.textContent)).toEqual(['A', 'B']);
});
});
如您所见,有一个超级简单的组件,它只显示Promise 提供的项目列表。有两种测试,一种失败,一种通过。这些测试之间的唯一区别是通过了两次调用fixture.detectChanges(); await fixture.whenStable(); 的测试。
更新:第二个例子(2019/03/21 再次更新)
这个例子试图调查与 ngZone 的可能关系:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { Component, NgZone } from '@angular/core';
@Component({
template: '{{value}}'
})
export class TestComponent {
valuePromise: Promise<ReadonlyArray<string>>;
value: string = '-';
set valueIndex(id: number) {
this.valuePromise.then(x => x).then(x => x).then(states => {
this.value = states[id];
console.log(`value set ${this.value}. In angular zone? ${NgZone.isInAngularZone()}`);
});
}
}
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [TestComponent],
providers: [
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
function diagnoseState(msg) {
console.log(`Content: ${(fixture.nativeElement as HTMLElement).textContent}, value: ${component.value}, isStable: ${fixture.isStable()} # ${msg}`);
}
it('using ngZone', async() => {
// setup
diagnoseState('Before test');
fixture.ngZone.run(() => {
component.valuePromise = Promise.resolve(['a', 'b']);
// execution
component.valueIndex = 1;
});
diagnoseState('After ngZone.run()');
await fixture.whenStable();
diagnoseState('After first whenStable()');
fixture.detectChanges();
diagnoseState('After first detectChanges()');
});
it('not using ngZone', async(async() => {
// setup
diagnoseState('Before setup');
component.valuePromise = Promise.resolve(['a', 'b']);
// execution
component.valueIndex = 1;
await fixture.whenStable();
diagnoseState('After first whenStable()');
fixture.detectChanges();
diagnoseState('After first detectChanges()');
await fixture.whenStable();
diagnoseState('After second whenStable()');
fixture.detectChanges();
diagnoseState('After second detectChanges()');
await fixture.whenStable();
diagnoseState('After third whenStable()');
fixture.detectChanges();
diagnoseState('After third detectChanges()');
}));
});
这些测试中的第一个(明确使用 ngZone)导致:
Content: -, value: -, isStable: true # Before test
Content: -, value: -, isStable: false # After ngZone.run()
value set b. In angular zone? true
Content: -, value: b, isStable: true # After first whenStable()
Content: b, value: b, isStable: true # After first detectChanges()
第二次测试日志:
Content: -, value: -, isStable: true # Before setup
Content: -, value: -, isStable: true # After first whenStable()
Content: -, value: -, isStable: true # After first detectChanges()
Content: -, value: -, isStable: true # After second whenStable()
Content: -, value: -, isStable: true # After second detectChanges()
value set b. In angular zone? false
Content: -, value: b, isStable: true # After third whenStable()
Content: b, value: b, isStable: true # After third detectChanges()
我有点期望测试在角度区域中运行,但事实并非如此。问题似乎来自于
为避免意外,传递给 then() 的函数永远不会被同步调用,即使是已经解决的 promise。 (Source)
在第二个示例中,我通过多次调用.then(x => x) 来引发问题,这只不过是将进度再次放入浏览器的事件循环中,从而延迟结果。到目前为止,据我所知,对await fixture.whenStable() 的调用基本上应该说“等到该队列为空”。正如我们所看到的,如果我明确地在 ngZone 中执行代码,这实际上是有效的。然而,这不是默认设置,我在手册中找不到任何地方打算这样编写测试,所以感觉很尴尬。
await fixture.whenStable() 在第二次测试中实际上做了什么? source code 表明在这种情况下 fixture.whenStable() 将只是 return Promise.resolve(false);。所以我实际上试图用await Promise.resolve() 替换await fixture.whenStable(),实际上它具有相同的效果:这确实具有暂停测试并从事件队列开始的效果,因此实际上执行了传递给valuePromise.then(...) 的回调,如果我只是经常就任何承诺致电await。
为什么我需要多次拨打await fixture.whenStable();?我用错了吗?这是预期的行为吗?是否有任何关于它打算如何工作/如何处理这个问题的“官方”文档?
【问题讨论】:
-
在我的应用程序中很多情况下我都有同样的问题并放弃了,只是添加了两次 :) 看看是否有人在这里解决它会很有趣!
-
这似乎与 promise 和 resolve 的工作方式有关。有趣的是,使用 observable 而不是你不需要触发 detectChanges 两次的 promise。知道为什么会很有趣。 stackblitz.com/edit/directive-testing-1bdxlz
标签: angular angular-test testbed