Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular unit testing form validation

I'm learning angular 8 and I'm using Karma for unit testing. I have created a basic registration form which works correctly but I'm facing issues in testing.

Upon testing I'm getting 2 failures

RegisterComponent > form should be valid Error: Expected validator to return Promise or Observable.

and

RegisterComponent > should call onSubmit method Error: : Expected a spy, but got FormGroup({ validator: Function, asyncValidator: null, _onCollectionChange: Function, pristine: true, touched: false, _onDisabledChange: [ ], controls: Object({ name: FormControl({ validator: Function, asyncValidator: null, _onCollectionChange: Function, pristine: true, touched: false, _onDisabledChange: [ Function ], _onChange: [ Function ], _pendingValue: '', value: '', status: 'INVALID', errors: Object({ required: true }), valueChanges: EventEmitter({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, __isAsync: false }), statusChanges: EventEmitter({ _isScalar: false, observers: [ ], closed: false, isStopped: false, hasError: false, thrownError: null, __isAsync: false }), _parent: }), email: FormControl({ validator: Function, asyncValidator: Function, _onCollectionChange: Function, pristine: true, touched: false, _onDisabledChange: [ Function ], _onChange: [ Function ], _pendingValue: '', value: '' .... Usage: expect().toHaveBeenCalledTimes()

register.component.ts

import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

import { AuthenticationService } from '@/_services';
import { MustMatch } from '@/_helpers/validators';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
})
@Component({ templateUrl: 'register.component.html' })
export class RegisterComponent implements OnInit {
  registerForm: FormGroup;
  submitted = false;
  returnUrl: string;
  error = '';

  constructor(
    private formBuilder: FormBuilder,
    private route: ActivatedRoute,
    private router: Router,
    private authenticationService: AuthenticationService
  ) {
    if (this.authenticationService.currentUserValue) {
      this.router.navigate(['/']);
    }
  }

  ngOnInit() {
    this.registerForm = this.formBuilder.group({
      name: ['', Validators.required],
      email: ['', Validators.required, Validators.email],
      phone: ['', Validators.required],
      password: ['', Validators.required, Validators.minLength(6)],
      confirmPassword: ['', Validators.required],
    }, {
        validator: MustMatch('password', 'confirmPassword')
      });

    this.returnUrl = this.route.snapshot.queryParams.returnUrl || '/';
  }

  get f() {
    return this.registerForm.controls;
  }

  onSubmit() {
    this.submitted = true;
    if (this.registerForm.invalid) {
      return;
    }

    this.submitted = false;
  }
}

register.component.spec.ts

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { RegisterComponent } from './register.component';
import { ActivatedRoute, Router } from '@angular/router';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { DebugElement } from '@angular/core';

describe('RegisterComponent', () => {
  let component: RegisterComponent;
  let fixture: ComponentFixture<RegisterComponent>;
  let de: DebugElement;
  let el: HTMLElement;
  const fakeActivatedRoute = {
    snapshot: {
      queryParams: {
        returnUrl: '/'
      }
    }
  };
  const routerSpy = jasmine.createSpyObj('Router', ['navigate']);

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ReactiveFormsModule, FormsModule, HttpClientTestingModule],
      declarations: [RegisterComponent],
      providers: [
        { provide: Router, useValue: routerSpy },
        { provide: ActivatedRoute, useFactory: () => fakeActivatedRoute }
      ]

    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(RegisterComponent);
      component = fixture.componentInstance;
      component.ngOnInit();
      fixture.detectChanges();
      de = fixture.debugElement.query(By.css('form'));
      el = de.nativeElement;
    });
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(RegisterComponent);
    component = fixture.componentInstance;
    component.ngOnInit();
    fixture.detectChanges();
  });

  it('form invalid when empty', () => {
    component.registerForm.controls.name.setValue('');
    component.registerForm.controls.email.setValue('');
    component.registerForm.controls.phone.setValue('');
    component.registerForm.controls.password.setValue('');
    component.registerForm.controls.confirmPassword.setValue('');
    expect(component.registerForm.valid).toBeFalsy();
  });

  it('username field validity', () => {
    const name = component.registerForm.controls.name;
    expect(name.valid).toBeFalsy();

    name.setValue('');
    expect(name.hasError('required')).toBeTruthy();

  });

  it('email field validity', () => {
    const email = component.registerForm.controls.email;
    expect(email.valid).toBeFalsy();

    email.setValue('');
    expect(email.hasError('required')).toBeTruthy();
  });

  it('phone field validity', () => {
    const phone = component.registerForm.controls.phone;
    expect(phone.valid).toBeFalsy();

    phone.setValue('');
    expect(phone.hasError('required')).toBeTruthy();

  });

  it('password field validity', () => {
    const password = component.registerForm.controls.password;
    expect(password.valid).toBeFalsy();

    password.setValue('');
    expect(password.hasError('required')).toBeTruthy();

  });

  it('confirmPassword field validity', () => {
    const confirmPassword = component.registerForm.controls.confirmPassword;
    expect(confirmPassword.valid).toBeFalsy();

    confirmPassword.setValue('');
    expect(confirmPassword.hasError('required')).toBeTruthy();

  });

  it('should set submitted to true', () => {
    component.onSubmit();
    expect(component.submitted).toBeTruthy();
  });

  it('should call onSubmit method', () => {
    spyOn(component, 'onSubmit');
    el = fixture.debugElement.query(By.css('button')).nativeElement;
    el.click();
    expect(component.registerForm).toHaveBeenCalledTimes(1);
  });

  it('form should be valid', () => {
    component.registerForm.controls.name.setValue('sasd');
    component.registerForm.controls.email.setValue('[email protected]');
    component.registerForm.controls.phone.setValue('132456789');
    component.registerForm.controls.password.setValue('qwerty');
    component.registerForm.controls.confirmPassword.setValue('qwerty');
    expect(component.registerForm.valid).toBeTruthy();
  });
});

I can't seem to understand what is causing this issue. Have gone through several docs and tutorials for this but it doesn't seem to work.

like image 798
Avinash Avatar asked Feb 04 '23 17:02

Avinash


2 Answers

Error: Expected validator to return Promise or Observable.

This means you added your multiple validators wrong. Instead of this:

this.registerForm = this.formBuilder.group({
          name: ['', Validators.required],
          email: ['', Validators.required, Validators.email],
          phone: ['', Validators.required],
          password: ['', Validators.required, Validators.minLength(6)],
          confirmPassword: ['', Validators.required],
        }

Try this:

this.registerForm = this.formBuilder.group({
          name: ['', Validators.required],
          email: ['', [Validators.required, Validators.email]],
          phone: ['', Validators.required],
          password: ['', [Validators.required, Validators.minLength(6)]],
          confirmPassword: ['', Validators.required],
        }

Notice how multiple validators are provided in an array, instead of just comma-seperated.

For your second error, you want to call

expect(component.onSubmit).toHaveBeenCalledTimes(1);

Instead of

expect(component.registerForm).toHaveBeenCalledTimes(1);
like image 196
skolldev Avatar answered Feb 07 '23 13:02

skolldev


expect(component.onSubmit).toHaveBeenCalledTimes(1)

Replace above line in below code, as you want to check onSubmit method not component.registerForm

it('should call onSubmit method', () => {
        spyOn(component, 'onSubmit');
        el = fixture.debugElement.query(By.css('button')).nativeElement;
        el.click();
        expect(component.registerForm).toHaveBeenCalledTimes(1);
    });
like image 42
Kinjal Avatar answered Feb 07 '23 12:02

Kinjal