Angular 中的单元测试无法读取 属性

Unit tests in Angular can't read property

我有一个下一个组件,其中包含用于在 Angular 9.1

上编写的注册表格
import { Component, OnInit, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, Validators, FormGroup, FormControl, FormGroupDirective, NgForm } from '@angular/forms';

@Component({
  selector: 'app-sign-up-form',
  templateUrl: './sign-up-form.component.html',
  styleUrls: ['./sign-up-form.component.scss']
})
export class SignUpFormComponent implements OnInit {
  @Output() submitedForm = new EventEmitter<any>();
  @Input() accountExist = false;
  public signUpForm: FormGroup;
  public hidePassword = true;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.signUpForm = this.formBuilder.group({
      email: [
        '',
        [
          Validators.required,
          Validators.pattern('^([A-z0-9_-]+\.)*[A-z0-9_-]+@[a-z0-9_-]+(\.[a-z0-9_-]+)*\.[a-z]{2,6}$')
        ]
      ],
      passwords: this.formBuilder.group({
        password: [
          '',
          [
          Validators.required,
          Validators.pattern('^[a-zA-Z0-9]{8,25}$'),
          ]
        ],
        confirmedPassword: [
          '',
          [
          Validators.required,
          Validators.pattern('^[a-zA-Z0-9]{8,25}$'),
          ]
        ]
        },
         {
          validators: [
            this.validatePasswords,
            Validators.required
          ]
        }
      )
    });
  }

  public onSubmit() {
    if (this.signUpForm.valid) {
      this.submitedForm.emit(this.signUpForm.value);
    }
  }

  public customErrorStateMatcher() {
    return {
      isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
        if (form.submitted && control.parent.invalid) {
          return true;
        } else if (control.invalid && control.touched) {
          return true;
        } else {
          return false;
        }
      }
    };
  }

  private validatePasswords(group: FormControl) {
    const password = group.get('password').value;
    const confirmPass = group.get('confirmedPassword').value;
    return password === confirmPass ? null : { notSame: true };
  }
}

还有模板:

<div class="sign-up">
  <h2 class="sign-up__header">Sign Up</h2>
  <form class="sign-up__form"
        [formGroup]="signUpForm">
    <div class="sign-up__form-fields-container">
      <mat-form-field class="sign-up__form-field"
                      hideRequiredMarker="true">
        <mat-label>Email</mat-label>
        <input placeholder="username@example.com"
               matInput
               formControlName="email" />
        <mat-error *ngIf="signUpForm.get('email').hasError('required')">
          You must enter a value
        </mat-error>
        <mat-error *ngIf="!signUpForm.get('email').hasError('required')">
          Not a valid email
        </mat-error>
      </mat-form-field>
      <div formGroupName="passwords">
        <mat-form-field class="sign-up__form-field">
          <mat-label style.color="white">Enter your password</mat-label>
          <input matInput
                 formControlName="password"
                 [type]="hidePassword ? 'password' : 'text'" />
          <mat-error *ngIf="signUpForm.get('passwords').get('password').hasError('required')">
            You must enter a value
          </mat-error>
          <mat-error *ngIf="!signUpForm.get('passwords').get('password').hasError('required')">
            Not a valid password
          </mat-error>
          <button type="button"
                  mat-icon-button
                  matSuffix
                  [attr.aria-label]="'Hide password'"
                  [attr.aria-pressed]="hidePassword"
                  (click)="hidePassword = !hidePassword">
            <mat-icon *ngIf="!hidePassword">visibility</mat-icon>
            <mat-icon *ngIf="hidePassword">visibility_off</mat-icon>
          </button>
        </mat-form-field>
        <mat-form-field appearance="legacy"
                        class="sign-up__form-field">
          <mat-label>Confirm password</mat-label>
          <input matInput
                 type="password"
                 formControlName="confirmedPassword"
                 [errorStateMatcher]="customErrorStateMatcher()" />
          <mat-error *ngIf="signUpForm.get('passwords').get('confirmedPassword').hasError('required')">
            You must enter a value
          </mat-error>
          <mat-error *ngIf="this.signUpForm.get('passwords').get('confirmedPassword').value.length">
            Password must match
          </mat-error>
        </mat-form-field>
      </div>
    </div>
    <div class="sign-up__button-wrapper">
      <button class="sign-up__submit"
              (click)="onSubmit()"><span>SUBMIT</span></button>
      <p *ngIf="accountExist"
         class="sign-up__custom-error">Account already exist</p>
    </div>
    <div class="sign-up__account-exist">
      <p class="sign-up__account-question">Already have an account ?</p>
      <a class="sign-up__sign-in-link"
         routerLink="/sign-in">Sign in</a>
      <a mat-button
         href="https://sportbuddies.herokuapp.com/oauth2/authorization/facebook"
         class="sign-up__facebook-btn">
        <span class="sign-up__facebook-icon"></span>
        <span>Sign up with Facebook</span></a>
    </div>
  </form>
  <div>
  </div>
</div>

所以我正在用 Jasmine/Karma 编写单元测试来测试组件的创建、提交表单、验证。

import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserModule, By } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DebugElement } from '@angular/core';

import { SignUpFormComponent } from './sign-up-form.component';

describe('SignUpFormComponent', () => {
  let component: SignUpFormComponent;
  let fixture: ComponentFixture<SignUpFormComponent>;
  let debug: DebugElement;
  let el: HTMLElement;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ SignUpFormComponent ],
      imports: [
        BrowserModule,
        FormsModule,
        ReactiveFormsModule
      ]
    })
    .compileComponents().then(() => {
      fixture = TestBed.createComponent(SignUpFormComponent);
      component = fixture.componentInstance;
      debug = fixture.debugElement.query(By.css('form'));
      el = debug.nativeElement;
    });
  }));

  it('should create the form', () => {
    fixture = TestBed.createComponent(SignUpFormComponent);
    const app = fixture.debugElement.componentInstance;
    expect(app).toBeTruthy();
  });

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

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

  it('form should be invalid', async(() => {
    fixture.detectChanges();
    component.signUpForm.controls['email'].setValue('');
    component.signUpForm.controls['password'].setValue('');
    component.signUpForm.controls['confirmedPassword'].setValue('');
    expect(component.signUpForm.valid).toBeFalsy();
  }));

  it('form should be valid', async(() => {
    component.signUpForm.controls['email'].setValue('hello@hello.com');
    component.signUpForm.controls['password'].setValue('1234567F');
    component.signUpForm.controls['confirmedPassword'].setValue('1234567F');
    expect(component.signUpForm.valid).toBeTruthy();
  }));
});

但它显示了我的错误

Failed: Cannot read property 'setValue' of undefined

Failed: Cannot read property 'controls' of undefined

Failed: Cannot read property 'valid' of undefined

那么,为什么这个 signUpForm 是未定义的,我该怎么办?谢谢。

尝试将 FormBuilder 添加到 TestBed.configureTestingModule 中的 providers 数组。但我敢打赌,真正的问题是您在创建组件时没有调用 fixture.detectChanges,因此 ngOnInit 不会 运行。 createComponent后第一个fixture.detectChanges是当ngOnInit运行s.

因此将 .then 更改为:

.compileComponents().then(() => {
      fixture = TestBed.createComponent(SignUpFormComponent);
      component = fixture.componentInstance;
      debug = fixture.debugElement.query(By.css('form'));
      el = debug.nativeElement;
      fixture.detectChanges(); // add fixture.detectChanges so ngOnInit runs on all later tests
    });

编辑:保留 fixture.detectChanges,但您看到 passwordsformBuilder.Group,并且您指的是 passwordconfirmedPasword。在您的 HTML 中,您可以使用 formGroupName="passwords" 访问它。

尝试:

component.signUpForm.controls['passwords'].controls['password'].setValue('1234567F');
component.signUpForm.controls['passwords'].controls['confirmedPassword'].setValue('1234567F');

无论哪种方式,您都在以错误的方式访问您想要访问的内容。

我用 2 个答案的一部分重写了这个 - 最后 - 有效:

 component.signUpForm.controls['email'].setValue('');
 component.signUpForm.controls['passwords'].get('password').setValue('');
 component.signUpForm.controls['passwords'].get('confirmedPassword').setValue('');