Search code examples
angulartestingngmodel

Angular testing and ngModel


I'm writing one of my first component test with Angular and I have some difficulties to make the ngModel binding work. Here is my test module definition:

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        LdapLoginComponent,
      ],
      imports: [
        CommonModule,
        FormsModule,
        NoopAnimationsModule,
        MatInputModule,
        MatFormFieldModule,
        RouterTestingModule,
      ],
      providers: [
        {
          provide: AuthorizationService,
          useValue: { login() {} },
        },
      ]
    }).compileComponents();
  }));

And here my test case:

it('should bind form fields with class', fakeAsync(() => {
    // Given
    const username = 'username';
    const password = 'password';
    const usernameField = de.query(By.css('input[name=username]')).nativeElement;
    const passwordField = de.query(By.css('input[name=password]')).nativeElement;

    // When
    usernameField.value = username;
    passwordField.value = password;
    usernameField.dispatchEvent(new Event('input'));
    passwordField.dispatchEvent(new Event('input'));
    tick();
    fixture.detectChanges();

    // Then
    expect(comp.username).toEqual(username);
    expect(comp.password).toEqual(password);
  }));

My component class:

export class LdapLoginComponent {

  username: string;
  password: string;
  errorMessage: string;
  submitDisabled = false;

  constructor(
    private authorizationService: AuthorizationService,
    private router: Router,
  ) {
  }

  login(): void {
    delete this.errorMessage;
    this.submitDisabled = true;
    this.authorizationService.login(AuthorizationProvider.LDAP, this.username, this.password)
      .subscribe(
        () => {
          this.router.navigate(['/']);
        },
        (err: Error) => {
          this.errorMessage = err.message;
          this.submitDisabled = false;
        },
      );
  }

}

And my component template:

<form class="form-container" (submit)="login()">
  <mat-form-field color="warn">
    <input
    matInput
    type="text"
    name="username"
    placeholder="Insert your username"
    [(ngModel)]="username"
    required
    i18n-placeholder="@@input.placeholder.username">
  </mat-form-field>
  <mat-form-field color="warn">
    <input
      matInput
      type="password"
      name="password"
      placeholder="Insert your password"
      [(ngModel)]="password"
      required
      i18n-placeholder="@@input.placeholder.password">
  </mat-form-field>
  <button
    mat-raised-button
    type="submit"
    color="warn"
    [disabled]="submitDisabled"
    i18n="@@input.submit">Submit</button>
</form>
<article>{{errorMessage}}</article>

I'm changing the value of the username and password fields inside my test and I'm expecting the username and password fields of my class to be updated accordingly. Everything works well if I test manually in my browser, but not in the test.

Any ideas ?

Thanks.


Solution

  • Looks like for mat-input fields, we need to do a focus() before changing the input:

    it('should bind form fields with class', fakeAsync(() => {
        // Given
        const username = 'username';
        const password = 'password';
        const usernameField = de.query(By.css('input[name=username]')).nativeElement;
        const passwordField = de.query(By.css('input[name=password]')).nativeElement;
    
        usernameField.focus();
        passwordField.focus();
    
        // When
        usernameField.value = username;
        passwordField.value = password;
        usernameField.dispatchEvent(new Event('input'));
        passwordField.dispatchEvent(new Event('input'));
        tick();
        fixture.detectChanges();
    
        // Then
        expect(comp.username).toEqual(username);
        expect(comp.password).toEqual(password);
    }));