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

时间:2018-05-02 15:00:03

标签: angular testing jasmine

我的任务是为使用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>

基本上,如果组件布尔变量'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);
}); 

我只是“模拟”组件变量,调用.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);
    });
    
  • 如果我从beforeEach()中删除.detectChanges()调用,并将其保留在测试用例中,则测试通过。

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

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

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

5 个答案:

答案 0 :(得分:14)

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

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

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

答案 1 :(得分:0)

我知道这个问题很老,但是最近我遇到了同样的问题,因为微调检测仅发生一次,微调器将不断在Karma页面上旋转。对我来说,解决方法是调用Fixture.detectChanges(true)还是Fixture.autoDetectChanges(true)。

beforeEach(() => { 
  fixture = TestBed.createComponent(TestComponent);
  component = fixture.componentInstance;
  component.titleInputEdit = true
  // 'detectChanges' will only test for onPush events: 
  // fixture.detectChanges();

  // 'autoDetectChanges' will continually check for changes until the test is complete.  
  // This is slower, but necessary for certain UI changes
  fixture.autoDetectChanges(true);

  debugElement = fixture.debugElement;
}); 

答案 2 :(得分:0)

在我的情况下,由于异步加载,我需要使用fixture.whenStable,而不仅仅是fixture.detectChanges,例如

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

    await fixture.whenStable();
}));

答案 3 :(得分:0)

更好的方法是使用 ChangeDetectionStrategy.Default 策略在规范本身中编写一个包装器组件,并通过父组件实例(即包装器)在规范中测试要测试的实际组件(即子组件)组件)。

子实例及其原生元素可以通过使用 fixture.debugElement

通过父的 fixture.debugElement.query(By.css('your-child-selector')) 访问

答案 4 :(得分:0)

直到今天仍在运行...

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

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

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

const cdr = debugEl.injector.get<ChangeDetectorRef>(ChangeDetectorRef as any);
cdr.detectChanges();

最后有一个最简单的解决方案,至少在我的脑海中,而且奇怪的是,我没有发现任何提及它。 因此,您可能已经知道您可以(或最终不得不)创建一个主机组件来包装您正在测试的组件,很多博客 for example 都展示了 {{1}如果 jasmine 能够真正感知到子组件中的变化,这将是完美的方法,但是,看起来,它并没有,我们坚持只在主机中列出输入并直接绑定它们的正常直观方法在测试组件的模板中,像这样:

@ViewChild(ComponentUnderTestComponent)

有了这个,现在你可以真正改变 HostComponent.someProperty 的值,然后调用 detectChanges() 和 jasmine 将完美地做它应该做的事情,并用更改更新 DOM:

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

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