6

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.

2 Answers 2

3

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);
}));
Sign up to request clarification or add additional context in comments.

Comments

1

The problem is that you are not actually setting the value of your input field before calling dispatchEvent. You are setting the component attribute directly.

comp.username = username;
usernameField.dispatchEvent(new Event('input'));

Should be

let usernameFieldElement = usernameField.nativeElement;
usernameFieldElement.value = username;
usernameField.dispatchEvent(new Event('input'));

and the same for password.

The other thing is you are testing three things at once, the entering of text into the input areas, clicking the button runs the logon and the logon function itself. I would suggest splitting these into three assertions.

  1. Set the fields as above and check the data binding.

  2. Click the button and spy on the login function to check it's been called.

  3. Set the actual component attributes and call logon() directly and check your navigateSpy has been called correctly.

This way if something goes wrong you will be able to find it a lot more easily.

1 Comment

The code contained a typo, you're right. And yes, I totally agree with your, I should split that test. I updated my post with the new code but I still have the same binding issue :-(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.