Karma + Ionic 4 - No value accessor for form control with name

I would like to know how to implement unit form tests with karma.
I’ve implemented a test but it always gives the following error: Error: No value accessor for form control with name: 'email'

signup.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Routes, RouterModule } from '@angular/router';
import { IonicModule } from '@ionic/angular';
import { SignupPage } from './signup.page';

const routes: Routes = [{ path: '', component: SignupPage  }];

@NgModule({
  imports: [CommonModule, FormsModule, ReactiveFormsModule, IonicModule, RouterModule.forChild(routes)],
  declarations: [SignupPage]
})
export class SignupPageModule {}

signup.page.html

<ion-header no-border>
  <ion-toolbar>

    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>

    <ion-title>
      <img src="../../../../assets/logo-colored.png">
    </ion-title>

    <ion-buttons slot="end">
      <div class="center-title-helper"></div>
    </ion-buttons>

  </ion-toolbar>
</ion-header>

<ion-content padding>

  <ion-progress-bar [hidden]="true" type="indeterminate"></ion-progress-bar>

  <img src="../../../../assets/logo-colored.png">

  <h3 text-center>Criar Conta</h3>

  <ion-list no-padding>
    <form [formGroup]="signupForm" (ngSubmit)="submitSignupForm()">

      <ion-item [ngClass]="{ 'ion-touched ion-invalid' : emailControl.invalid }">
        <ion-label position="floating">E-mail</ion-label>
        <ion-input formControlName="email" type="e-mail"></ion-input>
      </ion-item>

      <div *ngIf="emailControl.invalid" padding-start>
        <ion-note *ngIf="emailControl.hasError('required')" color="danger">
          Insira o seu endereço de e-mail
        </ion-note>
      </div>

      <ion-item [ngClass]="{ 'ion-touched ion-invalid' : passwordControl.invalid }">
        <ion-label position="floating">Senha</ion-label>
        <ion-input clearOnEdit="false" formControlName="password" type="password"></ion-input>
      </ion-item>

      <div *ngIf="passwordControl.invalid" padding-start>
        <ion-note *ngIf="passwordControl.hasError('required')" color="danger">
          Insira a sua senha
        </ion-note>
      </div>

      <ion-button [hidden]="true">Fake Button</ion-button>

    </form>
  </ion-list>

</ion-content>

<ion-footer>
  <ion-toolbar>
    <ion-button expand="block" shape="round" (click)="submitSignupForm()">Criar Conta</ion-button>
  </ion-toolbar>
</ion-footer>

signup.page.ts

import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.page.html',
  styleUrls: ['./signup.page.scss'],
})
export class SignupPage implements OnInit {

  signupForm: FormGroup;

  constructor() { }

  ngOnInit() {
    this.signupForm = new FormGroup({
      email: new FormControl(''),
      password: new FormControl(''),
    });
  }

  submitSignupForm() {
    if (this.validateForm) {

    }
  }

  get validateForm(): boolean {
    if (this.emailControl.value === '') { this.emailControl.setErrors({ required: true }); }
    if (this.passwordControl.value === '') { this.passwordControl.setErrors({ required: true }); }

    return this.signupForm.valid;
  }

  get emailControl(): AbstractControl {
    return this.signupForm.controls['email'];
  }

  get passwordControl(): AbstractControl {
    return this.signupForm.controls['password'];
  }

}

signup.page.spec.ts

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { SignupPage } from './signup.page';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ SignupPage ],
      imports: [
        FormsModule,
        ReactiveFormsModule
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
    })
    .compileComponents();
  }));

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

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

Full log when I run the ng test command

Error: No value accessor for form control with name: 'email'
    at _throwError (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/forms/fesm5/forms.js:2144:1)
    at setUpControl (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/forms/fesm5/forms.js:2054:1)
    at FormGroupDirective.push../node_modules/@angular/forms/fesm5/forms.js.FormGroupDirective.addControl (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/forms/fesm5/forms.js:5281:1)
    at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName._setUpControl (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/forms/fesm5/forms.js:5882:1)
    at FormControlName.push../node_modules/@angular/forms/fesm5/forms.js.FormControlName.ngOnChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/forms/fesm5/forms.js:5803:1)
    at checkAndUpdateDirectiveInline (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/core.js:22085:1)
    at checkAndUpdateNodeInline (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/core.js:23353:1)
    at checkAndUpdateNode (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/core.js:23315:1)
    at debugCheckAndUpdateNode (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/core.js:23949:1)
    at debugCheckDirectivesFn (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/fesm5/core.js:23909:1)

I was able to resolve this problem by importing IonicModule and RouterTestingModule.

The test class was this way.

signup.page.spec.ts

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { IonicModule } from '@ionic/angular';

import { SignupPage } from './signup.page';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ SignupPage ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
      imports: [
        ReactiveFormsModule,
        RouterTestingModule,
        IonicModule
      ],
      providers: [FormBuilder],
    })
    .compileComponents();
  }));

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

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

});

4 Likes

I am also facing the same issue i have try your above suggestion but doesn’t work, can you help me ?

For me, it worked by adding the ngDefaultControl directive to the <ion-input> element.

1 Like