Unit test when a button is enabled using Karma in Angular2

14,028

If you take a look at DefaultValueAccessor source code:

host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},

https://github.com/angular/angular/blob/2.4.2/modules/%40angular/forms/src/directives/default_value_accessor.ts#L36

you can notice that main your mistake is wrong event name.

You have to use input event instead of change

it('should check loginBtn is enabled after inputs check out', async(() => {
  fixture.detectChanges();
  fixture.whenStable().then(() => {
    userEmail.value = '[email protected]';
    userEmail.dispatchEvent(new Event('input'));

    userPassword.value = 'asdf';
    userPassword.dispatchEvent(new Event('input'));
    fixture.detectChanges();
    expect(loginBtn.disabled).toBe(false)
  });
}));

Plunker Example

Share:
14,028

Related videos on Youtube

Snowman
Author by

Snowman

Professional software developer. Science/history buff Cinephile/bookworm

Updated on June 04, 2022

Comments

  • Snowman
    Snowman almost 2 years

    I have an Angular2 project setup using angular CLI. I'm trying to test a form component. It has two fields: email and password. Both are required. There's a login button of the type submit. It's enabled only after the user has provided valid inputs in both the fields.

    <form (ngSubmit)="login()" #loginForm="ngForm">
    
            <md-input-container class="md-block">
                <input md-input [(ngModel)]="user.email" class="userEmail"
                    name="userEmail" type="email" placeholder="Email" 
                    ngControl="userEmail" 
                required>
            </md-input-container>
            <br>
    
            <md-input-container class="md-block">
                <input md-input [(ngModel)]="user.password" class="userPassword"
                    name="userPassword" type="password" placeholder="Password" 
                    ngControl="userPassword" 
                required>
            </md-input-container>
            <br>
    
    
    
            <!--the button is enabled only after all form fields are valid-->
            <button  color="primary" md-button 
                type="submit" class='loginButton'
            [disabled]="!loginForm.form.valid">
                Login
             </button>
    

    Now, I want to test the button using karma. I went through the docs and was able to test the input, by giving a random input during testing and verifying it:

    //imports...
    
    describe('LoginComponent (inline template)', () => {
      let comp:    LoginComponent;
      let fixture: ComponentFixture<TmLoginComponent>;
      let userEmail: HTMLInputElement;
      let userPassword: HTMLInputElement;
      let loginBtn: HTMLElement;
      let title: HTMLElement;
    
    
    
      beforeEach(() => {
    
        TestBed.configureTestingModule({
          declarations: [ LoginComponent ], // declare the test component
          imports: [ MaterialModule.forRoot(), FormsModule, 
                      RouterTestingModule.withRoutes(
                      [{path: 'login', component: LoginComponent}, ])
                    ],
          providers: [{provide: UserAuthenticationService, useValue: uaServiceStub }, CookieService],
    
        });
    
        fixture = TestBed.createComponent(LoginComponent);
    
        comp = fixture.componentInstance; //LoginComponent test instance
    
        // query by CSS element selector
        userEmail = fixture.debugElement.query(By.css('.userEmail')).nativeElement;
        userPassword = fixture.debugElement.query(By.css('.userPassword')).nativeElement;
        loginBtn = fixture.debugElement.query(By.css('.loginButton')).nativeElement;
    
    
    //tests
    
    //this test is successful
      it('should check initial input', () => {
        fixture.detectChanges();
        expect(userEmail.value).toBe('')
      });
    
    //this test is successful
      it('should check later input', async(() => {    
        fixture.detectChanges();
        fixture.whenStable().then(() => {
          userEmail.value = 'someValue';
          userEmail.dispatchEvent(new Event('change'));
    
          expect(userEmail.value).toBe('someValue');
        });
    
      }));
    
    //EDITED: NEW TEST
    it('should check loginBtn is disabled initially', () => {
      fixture.detectChanges();
      loginBtn =   fixture.debugElement.query(By.css('.loginButton')).nativeElement;
      fixture.whenStable().then(() => {
        expect(loginBtn.disabled).toBe(true)
       })
     });
    
    
    //this test fails. "Expected true to be false"
      it('should check loginBtn is enabled after inputs check out', async(() => {
       fixture.detectChanges();
       fixture.whenStable().then(() => {
       userEmail.value = '[email protected]';//valid
       userEmail.dispatchEvent(new Event('change'));
    
       userPassword.value = 'asdf';//vaild
       userPassword.dispatchEvent(new Event('change'));
       fixture.detectChanges();
       expect(loginBtn.disabled).toBe(false)
      })
     }));
    
    });
    

    I don't see why the test fails. Can anybody help?

  • Snowman
    Snowman over 7 years
    Thanks, friend. It worked. Now, it also returns true when I put the email as simply raj, whereas it should be valid only if there is an @ character followed by at least one other character (default conditions). What can I do to achieve that?
  • Snowman
    Snowman over 7 years
    Thanks. So it's a bug. Anyway, really appreciate the help, and the extra effort you put in setting up the plunker. Sincere thanks.
  • Snowman
    Snowman over 7 years
    Sorry to bother you again. See, my login button is disabled initially (don't see that in the plunker). I've another unit test: 'should check loginBtn is disabled initially', which I've edited the question to include. Now, this test passes on its own, and the 'after inputs check out' test passes on its own. But when I test them both simultaneously, the latter fails. Any idea what could be the reason?
  • yurzui
    yurzui over 7 years
    You need to call detectChanges again after whenStable plnkr.co/edit/vwyoQFBXWqYOid9eJCa9?p=preview
  • Snowman
    Snowman over 7 years
    It worked! Thanks a ton. Btw, why DO I need to call detectChanges() both before AND after whenStable? It fails if either is missing. Can't understand the theory.
  • yurzui
    yurzui over 7 years
    The first detectChanges is for setup controls, then we should wait until async actions within NgForm is executed (take.ms/TmB7N) but our template hasn't updated yet after these manipulations so we have to fire detectChanges to update our view with right state