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
,但您看到 passwords
为 formBuilder.Group
,并且您指的是 password
和 confirmedPasword
。在您的 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('');
我有一个下一个组件,其中包含用于在 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
,但您看到 passwords
为 formBuilder.Group
,并且您指的是 password
和 confirmedPasword
。在您的 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('');