gpt4 book ai didi

angular - 为什么我需要调用 detectChanges/whenStable 两次?

转载 作者:太空狗 更新时间:2023-10-29 16:58:43 26 4
gpt4 key购买 nike

第一个例子

我有以下测试:

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(); 两次。

更新:第二个例子(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()

我有点希望测试在 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.

detectChanges()


实现 自动更改检测 允许您在两个测试中仅调用一次 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']);

Automatic Change Detection


检查给出的各种示例提供了一个线索,说明为什么您需要在没有 AutoDetect 的情况下调用 fixture.detectChanges() 两次。第一次触发 Delayed change detection 模型中的 ngOnInit...第二次调用它更新 View 。

You can see this based on the comments to the right offixture.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');
}));

More async tests Example


总结:当不利用自动更改检测时,调用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 以演示以下变体的不同输出以供您查看。

  • 等待 fixture.whenStable();
  • fixture.whenStable().then(()=>{})
  • 等待 fixture.whenStable().then(()=>{})

堆栈 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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com