.detectChanges()在Angular测试中不起作用

LHB*_*LHB 7 testing jasmine angular

我的任务是为使用Angular开发的聊天应用程序编写测试.下面是Angular模板代码片段,我目前正在为以下代码编写测试:

<div class="title-menu-container" fxLayoutAlign="center center">
  <button id="save-title-button" mat-icon-button *ngIf="titleInputEdit; else settings">
    <mat-icon class="secondary-text" (click)="saveTitle(titleInput.value)">check</mat-icon>
  </button>
  <ng-template #settings>
    <button mat-icon-button [matMenuTriggerFor]="menu" [disabled]="!(isGroupConversation$ | async)">
      <mat-icon class="secondary-text">settings</mat-icon>
    </button>
  </ng-template>
</div>
Run Code Online (Sandbox Code Playgroud)

实际上,如果组件布尔变量'titleInputEdit'为true,则显示save-title-button,否则显示设置按钮.以下是导致问题的测试用例:

it('save title button should be present', () => {
  component.titleInputEdit = true;
  fixture.detectChanges();
  expect(fixture.nativeElement.querySelector('#save-title-button')).not.toBe(null);
}); 
Run Code Online (Sandbox Code Playgroud)

我只是"模拟"组件变量,调用.detectChanges(),然后测试是否存在按钮.但是,测试失败并显示"预期的空值不为空".

通过各种console.log调用,我已经确认component.titleInputEdit正确设置为true但fixture.nativeElement不包含正确的按钮.

我注意到的一些事情:

  • 如果我将'component.titleInputEdit = true'行移动到我的beforeEach中并将其删除,并从我的测试中调用detectChanges(),则测试通过.

    beforeEach(() => {
      fixture = TestBed.createComponent(TestComponent);
      component = fixture.componentInstance;
      component.titleInputEdit = true
      fixture.detectChanges();
      debugElement = fixture.debugElement;
    });     
    
    it('save title button should be present', () => {
        expect(fixture.nativeElement.querySelector('#save-title-button')).not.toBe(null);
    });
    
    Run Code Online (Sandbox Code Playgroud)
  • 如果我从beforeEach()中删除.detectChanges()调用,并将其保留在测试用例中,则测试通过.

我对Angular比较陌生,但我发现有类似问题的人的情况.在尝试了那些帖子中推荐的一些东西后,我仍然在摸不着头脑.更奇怪的是,我已经为其他Angular组件编写了测试,这些组件几乎完全相同,没有任何问题.

Angular文档中提供的示例也显示了非常相似的内容:

it('should display a different test title', () => {
  component.title = 'Test Title';
  fixture.detectChanges();
  expect(h1.textContent).toContain('Test Title');
});    
Run Code Online (Sandbox Code Playgroud)

小智 22

今天,Angular 在 Angular v14.1 中解决了这个问题。您必须通过方法在测试中设置组件的输入fixture.componentRef.setInput,以便组件将被标记为脏并且可以运行 detectorChanges。


LHB*_*LHB 19

事实证明这是由于在组件中使用ChangeDetectionStrategy.OnPush.使用OnPush只允许您调用.detectChanges()一次,因此后续调用将无法执行任何操作.我对Angular不够熟悉,无法完全理解为什么.

通过覆盖TestBed配置中的ChangeDetectionStrategy,我能够产生所需的行为.

TestBed.configureTestingModule({
    imports: [],
    declarations: [TestComponent],
    providers: []
  })
    .overrideComponent(TestComponent, {
      set: { changeDetection: ChangeDetectionStrategy.Default }
    })
    .compileComponents();
Run Code Online (Sandbox Code Playgroud)

  • 抱歉,答案有些误导。“ OnPush”策略指示仅在组件的父级更新组件输入之一时才标记您的组件以进行检查。Angular无法跟踪在测试中手动设置的属性,因此在DOM中看不到任何更改。如果`titleInputEdit`是输入,那么在测试中,您将创建一个主机组件并将其属性绑定到`titleInputEdit`。仅仅使用OnPush仅允许您一次调用.detectChanges()的说法并不是100%正确。在此之前,应该有一些东西标记您的组件以供检查。 (2认同)

小智 12

直到今天还在坚持这个...

我个人喜欢最重要的ChangeDetectionStrategy 解决方案,因为它是 TestBed 设置中的一次性解决方案,但我知道这种侵入式解决方案并不理想。

TestBed.configureTestingModule({
    imports: [],
    declarations: [TestComponent],
    providers: []
})
.overrideComponent(TestComponent, {
    set: { changeDetection: ChangeDetectionStrategy.Default }
})
.compileComponents();
Run Code Online (Sandbox Code Playgroud)

我已经看到“ChangeDetectorRef”解决方案被用在 Component 类本身上,并带有“changeDetector.markForCheck()”,这不是一个好方法,因为您的组件不必适应测试,但您仍然可以使用它解决方案,通过调用而不是正常的“detectChanges()”,而不弄乱实际组件,如此处所示

const cdr = debugEl.injector.get<ChangeDetectorRef>(ChangeDetectorRef as any);
cdr.detectChanges();
Run Code Online (Sandbox Code Playgroud)

最后有一个最简单的解决方案,至少在我的脑海中,但奇怪的是,我没有发现任何提及它的内容。因此,您可能已经知道您可以(或最终必须)创建一个主机组件来包装您正在测试的组件,例如,很多博客都展示了一种方法的使用@ViewChild(ComponentUnderTestComponent),如果 jasmine 则该方法将是完美的实际上可以感知到子组件中的变化,但是,正如它看起来的那样,事实并非如此,我们坚持使用正常的直观方法,即仅列出主机中的输入并将它们直接绑定到测试组件的模板中,像这样:

@Component({
    template: `<component-tag [(ngModel)]="val" [someProperty]="flag"></component-tag>`
})
class HostComponent {
    val: number;
    flag: boolean = false;
}
Run Code Online (Sandbox Code Playgroud)

有了这个,现在你实际上可以更改 HostComponent.someProperty 的值,然后调用 detectorChanges() ,并且 jasmine 将完美地完成它应该做的事情,并用更改更新 DOM:

fixture.componentInstance.readonly = true;
fixture.detectChanges();
Run Code Online (Sandbox Code Playgroud)

现在,如果您的组件继续运行并具有数十个输入属性,那么我想这并不是真正可行,但无论如何,我想我会把它扔在那里,享受


Mic*_*ael 5

就我而言,由于异步加载,我需要使用fixture.whenStable而不仅仅是fixture.detectChanges,例如

it('test description', async(async () => {

    await fixture.whenStable();
}));
Run Code Online (Sandbox Code Playgroud)