Dat Le
Dat Le

Reputation: 58

Angular unit test failed due to data in HTML NgModel binded to a service's property not updated

I have a LoginPageComponent with email and password fields whose values areNgModel binded to LoginServices's property loginInfo. LoginPageComponent also has Login button, which is disabled when loginInfo.isReadyToSubmit() returns false (when either email or password is empty).

In my unit test for LoginPageComponent, I want to verify that Login button is enabled when loginInfo.isReadyToSubmit() returns true.

I tried dispatchEvent(), detectChange(), and fixture.whenStable().then() but nothing seems to work. How should I proceed from here? I am happy to take correction/criticism in other aspects of my code.

LoginPageComponent:

export class LoginPageComponent {
  constructor(
    protected loginServices: LoginServices,
    private router: Router,
    private toastServices: ToastServices
  ) {}
protected loginInfo: LoginInfo = this.loginServices.loginInfo;

LoginPageComponent HTML:

<div class="route-container">
  <spinner [isDisplayed]="loginServices.isLoggingIn()" />

  <form #loginForm="ngForm" (ngSubmit)="onSubmit()">
    <label>
      Email <br />
      <input type="email" [(ngModel)]="loginInfo.email" name="email" />
    </label>
    <br /><br />
    <label>
      Password <br />
      <input type="password" [(ngModel)]="loginInfo.password" name="password" />
    </label>
    <br /><br />
    <div class="center">
      <button
        type="submit"
        [disabled]="!loginInfo.isReadyToSubmit() || loginServices.isLoggingIn()"
      >
        Login
      </button>
    </div>
  </form>
</div>

LoginServices:

export class LoginServices {
  constructor(private http: HttpClient) {}
  public loginInfo: LoginInfo = new LoginInfo('', '');
}

LoginInfo:

export class LoginInfo {
  constructor(public email: string, public password: string) {}

  public isReadyToSubmit(): boolean {
    return this.email !== '' && this.password !== '';
  }
}

LoginPageComponent spec:

describe('LoginPageComponent', () => {
  let component: LoginPageComponent;
  let fixture: ComponentFixture<LoginPageComponent>;
  let emailInput: HTMLInputElement;
  let passwordInput: HTMLInputElement;
  let loginButton: HTMLButtonElement;
  let loginServices: LoginServices;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        HttpClientModule,
        ToastrModule.forRoot(),
        MatProgressSpinnerModule,
      ],
      declarations: [NgModel, NgForm, SpinnerComponent, LoginPageComponent],
      providers: [{ provide: LoginServices }],
    }).compileComponents();

    fixture = TestBed.createComponent(LoginPageComponent);
    component = fixture.componentInstance;
    loginServices = TestBed.inject(LoginServices);
    fixture.detectChanges();

    const nativeElement: HTMLElement = fixture.nativeElement;
    emailInput = nativeElement.querySelector('input[name="email"]')!;
    passwordInput = nativeElement.querySelector('input[name="password"]')!;
    loginButton = nativeElement.querySelector('button')!;
  });

it('should enable Login button when there are email and password inputs', async () => {
    const data = {
      email: '[email protected]',
      password: '123',
    };

    emailInput.value = data.email;
    emailInput.dispatchEvent(new Event('input'));
    fixture.detectChanges();

    passwordInput.value = data.password;
    passwordInput.dispatchEvent(new Event('input'));
    fixture.detectChanges();

    fixture.whenStable().then(() => {
      
      expect(loginButton.disabled).toBeFalse();
    });
  });

Upvotes: 1

Views: 38

Answers (1)

Naren Murali
Naren Murali

Reputation: 56052

NgModel will work fine so there is no need to trigger an artificial event, we can directly update the properties that are binded to ngModel and then check disabled.

  it('should enable Login button when there are email and password inputs', async () => {
    const data = {
      email: '[email protected]',
      password: '123',
    };

    component.loginInfo.email = data.email;
    component.loginInfo.password = data.password;
    fixture.detectChanges();

    fixture.whenStable().then(() => {
      expect(loginButton.disabled).toBeFalse();
    });
  });

Upvotes: 0

Related Questions