- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我有以下测试:
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']);
});
});
如您所见,有一个 super 简单的组件,它只显示 Promise
提供的项目列表。有两个测试,一个失败,一个通过。这些测试之间的唯一区别是通过的测试调用 fixture.detectChanges();等待 fixture.whenStable();
两次。
这个例子试图调查与 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()
我有点希望测试在 Angular 区域运行,但事实并非如此。问题似乎来自于
To avoid surprises, functions passed to then() will never be called synchronously, even with an already-resolved promise. (Source)
在第二个示例中,我通过多次调用 .then(x => x)
引发了问题,这只会将进度再次放入浏览器的事件循环中,从而延迟结果。到目前为止,根据我的理解,对 await fixture.whenStable()
的调用基本上应该说“等到该队列为空”。正如我们所见,如果我明确地执行 ngZone 中的代码,这实际上是有效的。然而,这不是默认设置,我在手册中找不到任何地方表明我打算以这种方式编写我的测试,所以这感觉很尴尬。
await fixture.whenStable()
在第二个测试中实际上做了什么? source code表明在这种情况下,fixture.whenStable()
只会返回 Promise.resolve(false);
。所以我实际上尝试将 await fixture.whenStable()
替换为 await Promise.resolve()
并且确实具有相同的效果:这确实具有暂停测试的效果并从事件队列开始,因此传递给 valuePromise.then(...)
的回调实际上被执行了,如果我只是足够频繁地对任何 promise 调用 await
.
为什么我需要多次调用 await fixture.whenStable();
?我用错了吗?这是预期的行为吗?是否有关于它的工作原理/如何处理的任何“官方”文档?
最佳答案
我相信您遇到了延迟更改检测
。
Delayed change detection is intentional and useful. It gives thetester an opportunity to inspect and change the state of the componentbefore Angular initiates data binding and calls lifecycle hooks.
实现 自动更改检测
允许您在两个测试中仅调用一次 fixture.detectChanges()
。
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestComponent],
providers:[{ provide: ComponentFixtureAutoDetect, useValue: true }] //<= SET AUTO HERE
})
.compileComponents();
}));
堆栈 Blitz
https://stackblitz.com/edit/directive-testing-fnjjqj?embed=1&file=app/app.component.spec.ts
Automatic Change Detection
示例中的这条评论很重要,以及为什么您的测试仍然需要调用 fixture.detectChanges()
,即使使用了 AutoDetect
.
The second and third test reveal an important limitation. The Angulartesting environment does not know that the test changed thecomponent's title. The ComponentFixtureAutoDetect service responds toasynchronous activities such as promise resolution, timers, and DOMevents. But a direct, synchronous update of the component property isinvisible. The test must call fixture.detectChanges() manually totrigger another cycle of change detection.
由于您在设置 Promise 时解决它的方式,我怀疑它被视为同步更新并且 Auto Detection Service
不会响应它。
component.values = Promise.resolve(['A', 'B']);
检查给出的各种示例提供了一个线索,说明为什么您需要在没有 AutoDetect
的情况下调用 fixture.detectChanges()
两次。第一次触发 Delayed change detection
模型中的 ngOnInit
...第二次调用它更新 View 。
You can see this based on the comments to the right of
fixture.detectChanges()
in the code example below
it('should show quote after getQuote (fakeAsync)', fakeAsync(() => {
fixture.detectChanges(); // ngOnInit()
expect(quoteEl.textContent).toBe('...', 'should show placeholder');
tick(); // flush the observable to get the quote
fixture.detectChanges(); // update view
expect(quoteEl.textContent).toBe(testQuote, 'should show quote');
expect(errorMessage()).toBeNull('should not show error');
}));
总结:当不利用自动更改检测
时,调用fixture.detectChanges()
将“逐步”通过延迟更改检测
模型...让您在 Angular 启动数据绑定(bind)和调用生命周期 Hook 之前检查和更改组件状态的机会。
另请注意所提供链接中的以下评论:
Rather than wonder when the test fixture will or won't perform changedetection, the samples in this guide always call detectChanges()explicitly. There is no harm in calling detectChanges() more oftenthan is strictly necessary.
Stackblitz 的第二个例子
第二个示例 stackblitz 显示注释掉第 53 行 detectChanges()
导致相同的 console.log
输出。不必在 whenStable()
之前调用 detectChanges()
两次。您正在调用 detectChanges()
三次,但 whenStable()
之前的第二次调用没有任何影响。在您的新示例中,您只能从两个 detectChanges()
中真正获得任何东西。
There is no harm in calling detectChanges() more often than is strictly necessary.
https://stackblitz.com/edit/directive-testing-cwyzrq?embed=1&file=app/app.component.spec.ts
更新:第二个例子(2019/03/21再次更新)
提供 stackblitz 以演示以下变体的不同输出以供您查看。
堆栈 Blitz
https://stackblitz.com/edit/directive-testing-b3p5kg?embed=1&file=app/app.component.spec.ts
关于angular - 为什么我需要调用 detectChanges/whenStable 两次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55195739/
我正在使用 ng2-toastr 并收到以下错误 https://www.npmjs.com/package/ng2-toastr Attempt to use a destroyed view: d
有以下 2 个实体,具有以下属性: Parent ID Children Child ID ParentID Parent 现在我有以下代码: db.Confi
这个问题在这里已经有了答案: Angular 2 - View not updating after model changes (5 个答案) 关闭 6 个月前。 我正在开发 Angular 4
我使用的是 Angular2 的最终版本。 fixture.detectChanges() 调用了哪些生命周期事件,调用顺序是什么? 这方面的文档在哪里? 最佳答案 使用 fixture.detect
我正在尝试测试一个组件调用 detectChanges上面注入(inject)了ChangeDetectorRef 我已经逐步完成了代码,它肯定被调用了,但似乎我在组件和测试中得到了不同的 Chang
我将从以下概念开始这个问题:我在 StackOverflow 上看到了一个类似的问题,但该问题只回答了差异。 我想问的是我应该根据情况使用什么以及一种或另一种方法可能有什么缺点。 我知道 detect
说, - 有一个父组件 A 和一个子组件 B。 在组件B上设置OnPush。 有一刻,B 的一个不是输入绑定(bind)属性的属性发生变化,我想检测该变化并相应地更新 View 。 根据我的理解,应该
我正在复制 Angular 文档的一些示例以提高我对 Angular 单元测试的理解,但当我无法弄清楚发生了什么时,我最终进入了一个简单的测试用例。 这是我的 app.component.ts 文件,
我正在使用 Angular Meteor 2 创建一个简单的 UI。 1) 我有一个顶部导航栏组件,它有一个“注销”按钮。 2) 单击“注销”按钮时,它会重定向到“登录”。 3) 然后我在控制台中看到
我在测试中遇到 cdr.detectChanges() 问题。调用时发生错误,我没有任何信息,只是收到此错误: ZoneAwareError@webpack:///~/zone.js/dist/zon
我在 jasmine 中有以下代码: it('should pass on writing secondvalue in the input', async(() => { con
在 Angular 区域之外工作时,有两种方法可以检测变化 - 通过使用 NgZone.run 或使用 ChangeDetectorRef.detectChanges 方法重新进入区域。 NgZone
我的任务是为使用 Angular 开发的聊天应用程序编写测试。下面是我目前正在为其编写测试的 Angular 模板代码片段: check settin
阅读 How to use RxJs distinctUntilChanged?和 this ,似乎 distinctUntilChanged 将输出流更改为仅提供不同的连续值。 我的意思是,如果相同
第一个例子 我有以下测试: import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { Com
ChangeDetectorRef.markForCheck()有什么区别和 ChangeDetectorRef.detectChanges() ? 我只有found information on S
所以我的应用程序一直给我这个错误: extensions::uncaught_exception_handler:8 Error in event handler for runtime.onMess
我正在阅读 this文章中包含有关何时使用的部分 markForChange() . 在他的例子中,他有以下组件: @Component({ selector: 'cart-badge', t
我对 context.SaveChanges 是否会自动调用 DetectChanges 感到困惑。大多数有关 Entity Framework 的书籍和博客都说会。但我的简单代码片段。看起来 Sav
我正在用 Jasmine 和 Karma 学习单元测试。我的测试用例正在通过,但我不明白一件事。这是我的 typescript : // array list of objects where eac
我是一名优秀的程序员,十分优秀!