angulardata-bindingkarma-jasminecomponent-testing

detectChanges() not working in Angular standalone component test


I have written a component test for a simple component which only displays an input field for a property name.

In the component test I'm setting the component's input property name to 'Stephan' and I'm expecting my name to appear in the input field, but it doesn't. In the test I have wrapped my component in a host component (as suggested in multiple tutorials and similar questions on Stack Overflow).

I tried various combinations of detectChanges()/whenStable() calls, but none of them worked.

name.component.html

<input id="name" [(ngModel)]="name" />

name.component.ts

import { Component, Input } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'name',
  templateUrl: './name.component.html',
  standalone: true,
  imports: [FormsModule],
})
export class NameComponent {
  @Input() name: string = '';
}

name.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NameComponent } from './name.component';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';

describe('NameComponent', () => {
  @Component({
    imports: [NameComponent],
    standalone: true,
    template: `<name [name]="name"></name>`,
  })
  class TestHostComponent {
    name: string = '';
  }

  let hostComponent: TestHostComponent;
  let hostFixture: ComponentFixture<TestHostComponent>;

  beforeEach(async () => {
    hostFixture = TestBed.createComponent(TestHostComponent);
    hostComponent = hostFixture.componentInstance;
    hostFixture.detectChanges();
  });

  it('should display my name', async () => {
    hostComponent.name = 'Stephan';
    hostFixture.detectChanges();

    const input = hostFixture.debugElement.query(By.css('input[id="name"]'));
    console.log(input.nativeElement);
    expect(input.nativeElement.textContent).toEqual('Stephan');
  });
});

You can try it on Stackblitz here: https://stackblitz.com/edit/stackblitz-starters-gqas1y?file=src%2Fname-component%2Fname.component.spec.ts


Solution

  • Firstly <input> element has .value, not .textContent. Secondly, ngModel has some async code inside which is there to handle control state and to be able to use it in the parent template like control.valid, .prestine and similar.

    So the fixed tests would look like this:

    it('should display my name', async () => {
        hostComponent.name = 'Stephan';
        hostFixture.detectChanges();
        await hostFixture.whenStable();
    
        const input = hostFixture.debugElement.query(By.css('input[id="name"]'));
        console.log(input.nativeElement);
        expect(input.nativeElement.value).toEqual('Stephan');
      });