Failed: Cannot read property 'setValue' of undefined for a login test

Viewed 9823

I have written the following tests:

import { DebugElement } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AuthenticationService } from '../services/authentication.service';
import { By } from '@angular/platform-browser';
import { LoginComponent } from './login.component';
import { ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { RouterTestingModule } from '@angular/router/testing';
import { GlobalDataService } from '../services/global-data.service';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { DatePipe } from '@angular/common';
import { MatDialogModule } from '@angular/material/dialog';
import { OverlayModule } from '@angular/cdk/overlay';


describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;

  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      imports: [ ReactiveFormsModule, MatFormFieldModule, MatProgressSpinnerModule, RouterTestingModule, HttpClientTestingModule,
         OverlayModule, MatDialogModule ],
      providers: [ GlobalDataService, DatePipe, AuthenticationService ]
    })
    .compileComponents();
  }));

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

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should set instance correctly', () => {
    expect(component).not.toBeNull();
   });

  it('should call auth login method', waitForAsync(() => {
    let loginElement: DebugElement;
    const debugElement = fixture.debugElement;
    const authenticationService = debugElement.injector.get(AuthenticationService);
    const loginSpy = spyOn(authenticationService, 'login').and.callThrough();
    loginElement = fixture.debugElement.query(By.css('form'));
    // to set values
    component.loginForm.controls.username.setValue('test@test.com');
    component.loginForm.controls.password.setValue('testpassword');
    loginElement.triggerEventHandler('ngSubmit', null);
    expect(loginSpy).toHaveBeenCalledTimes(1); // check that service is called once
    // ^ this fails without correct login!
   }));

  it('should render "Login" at the top of the login page', () => {
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;
    expect(compiled.querySelector('h5').textContent).toContain('Login');
  });
});

My Login.component.ts:

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

import { AuthenticationService } from '../services/authentication.service';
import { GlobalDataService } from '../services/global-data.service';

/* tslint:disable variable-name */
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  returnUrl: string;
  loginForm: FormGroup;
  submitted = false;
  error = '';
  loading = false;
  public errorMsg = 'Please login to continue';
  public redirected: boolean;
  public utm_source: string;

  constructor(private router: Router, private formBuilder: FormBuilder, public DataService: GlobalDataService,
              public authenticationService: AuthenticationService, private activatedRoute: ActivatedRoute) {
      this.activatedRoute.queryParams.subscribe(params => {
      const param = params.utm_source;

      if ([ 'a', 'b', 'c', 'd', 'e'].includes(param)) {
        this.redirected = true;
        this.utm_source = param;
      } else {
        this.redirected = false;
      }
  });
  }

  ngOnInit(): void {
    this.loginForm = this.formBuilder.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]]
  });
  }

// convenience getter for easy access to form fields
get f() { return this.loginForm.controls; }

  onSubmit(loginsubmit) {
    this.submitted = true;
    // stop here if form is invalid
    if (this.loginForm.invalid) {
        return console.log('LoginForm Invalid');
    }
    this.loading = true;

    this.authenticationService.login(this.f.email.value, this.f.password.value)
        .pipe()
        .subscribe(
            data => {
              // it was this.returnUrl instead of avior/dashboard
                if (this.redirected) {
                  this.router.navigate([this.utm_source]);
                } else {
                  this.router.navigate(['landing-page']);
                }
            },
            error => {
                console.log('Login->authservice->err: ', error);
                this.error = error;
                this.loading = false;
            });
}
}

The error I get happens on the should call auth login method test with the following error:

Failed: Cannot read property 'setValue' of undefined

So it appears that the LoginForm form is somewhy and somehow undefined. Any clue why that might be? The definition of the form field appears to be fine, I have honestly no clue what's wrong. The most weird part is that it used to work and now it doesn't after migrating the code. I tried mendling with the code with no avail.

2 Answers

In you test setup you are trying to set a value on a control that doesn't exist in your form.

component.loginForm.controls.username.setValue('test@test.com'); // username doesn't exisit

the property is called email. try to change the line to:

component.loginForm.controls.email.setValue('test@test.com');

I had similar error:

Ensure to to initialize the form creation and patch under constructor and ngOnInit respectively i.e

 // creation goes in constructor
  dataFormGroup:FormGroup
  constructor(private formBuilder: FormBuilder) {
    this.dataFormGroup = this.formBuilder.group({
     ......
     ..........
   })

// patch/setvalue goes under ngOnInit
ngOnInit() {
  let urlData = history.state
  this.mode = urlData.mode
  this.update_data = urlData.user
  if (this.mode == "edit") {
    this.dataFormGroup.patchValue({
      first_name: this.update_data.first_name,
      last_name: this.update_data.last_name,
      ...
      .....
    });
  }
}
Related