1

I have written a component test for a simple custom form control component 'my-input' (implementing the ControlValueAccessor interface) which only contains an input field.

In the component test I'm handing over a value for the inner input field via 'ngModel' and I'm expecting the value to appear in the input field, but it doesn't.

my-input.component.html

<input [(ngModel)]="value" />

my-input.component.ts

import { Component, forwardRef } from '@angular/core';
import {
  ControlValueAccessor,
  FormsModule,
  NG_VALUE_ACCESSOR,
} from '@angular/forms';

@Component({
  selector: 'my-input',
  templateUrl: './my-input.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MyInputComponent),
      multi: true,
    },
  ],
  standalone: true,
  imports: [FormsModule],
})
export class MyInputComponent implements ControlValueAccessor {
  inputValue?: string;

  onChange: any = () => {};
  onTouch: any = () => {};

  public get value(): string {
    return this.inputValue || '';
  }

  public set value(value: string) {
    this.inputValue = value;
    this.onChange(this.inputValue);
    this.onTouch(this.inputValue);
  }

  writeValue(value: string) {
    this.inputValue = value;
    this.onChange(this.value);
  }

  registerOnChange(fn: any) {
    this.onChange = fn;
  }

  registerOnTouched(fn: any) {
    this.onTouch = fn;
  }
}

my-input.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { MyInputComponent } from './my-input.component';
import { Component, DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

describe('MyInputComponent', () => {
  @Component({
    imports: [MyInputComponent, FormsModule],
    standalone: true,
    template: `<my-input [(ngModel)]="someValue"></my-input>`,
  })
  class TestHostComponent {
    someValue?: string;
  }

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

  beforeEach(async () => {
    await TestBed.compileComponents();
  });

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

  describe('when setting a value via ngModel', () => {
    it('should set its value correctly', async () => {
      const inputElement: HTMLInputElement = componentDe.query(
        By.css('input')
      ).nativeElement;

      expect(inputElement).toBeDefined();
      expect(inputElement.value).toEqual('');

      hostComponent.someValue = 'Hello';

      hostFixture.detectChanges();
      await hostFixture.whenStable();

      expect(inputElement.value).toEqual('Hello');
    });
  });
});

I tested 'my-input' component in an Angular app, which worked just fine:

main.ts

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import 'zone.js';
import { MyInputComponent } from './my-input-component/my-input.component';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [MyInputComponent, FormsModule],
  template: `
    <h1>Hello from {{ name }}!</h1>
    <my-input [(ngModel)]="name"></my-input>
  `,
})
export class App {
  name = 'Angular';
}

bootstrapApplication(App);

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

1 Answer 1

1

For some reason, I need to call hostFixture.detectChanges(); await hostFixture.whenStable(); twice, to make the test succeed.

it('should set its value correctly', async () => {
     const inputElement: HTMLInputElement = componentDe.query(
       By.css('input')
     ).nativeElement;

     expect(inputElement).toBeDefined();
     expect(inputElement.value).toEqual('');

     hostComponent.someValue = 'Hello';

     hostFixture.detectChanges();
     await hostFixture.whenStable();
     hostFixture.detectChanges();
     await hostFixture.whenStable();

     expect(inputElement.value).toEqual('Hello');
});
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.