Karma + Ionic 4 - 没有名称的表单控件的值访问器

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

我想知道如何用 karma 实现单元形式测试。 我已经实施了一个测试,但它总是给出以下错误: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();
  });
});

我 运行 ng test 命令时的完整日志

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)

我通过导入 IonicModuleRouterTestingModule 解决了这个问题。

测试class是这样的。

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();
  });

});

对我来说,当我将 RouterTestingModule 作为 RouterTestingModule.withRoutes([]) 导入数组时它起作用了。

  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';
    

    import { RouterTestingModule } from '@angular/router/testing';

    
    describe('SignupPage', () => {
      let component: SignupPage;
      let fixture: ComponentFixture<SignupPage>;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [ SignupPage ],
          imports: [
            FormsModule,
            ReactiveFormsModule,
RouterTestingModule.withRoutes([])
          ],
          schemas: [CUSTOM_ELEMENTS_SCHEMA],
        })
        .compileComponents();
      }));
    
      beforeEach(() => {
        fixture = TestBed.createComponent(SignupPage);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it('should create', () => {
        expect(component).toBeTruthy();
      });
    });