为什么在 Jasmine Angular 单元测试中没有触发反应式表单控件的更改事件?
Why is change event for reactive form control not firing in Jasmine Angular unit test?
我有一个更改函数,我正在传递给 反应式表单控件 的更改事件,它评估 脏 状态并检查如果控件上有任何错误,如果是,则将布尔标志设置为 true/false。然后使用此布尔标志来确定是否显示具有错误消息的 <div>
元素。这在浏览器中工作得很好,但是当单元测试运行时,"dirty" 永远不会被设置为 true。这是我的代码:
HTML
<form [formGroup]="myForm" novalidate>
<input id="age" formControlName="age" (change)="onChange()" />
<div id="ageError" *ngIf="ageIsError()">
<label>Age has errored</label>
</div>
</form>
组件
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
age: [null, [Validators.min(18)]]
});
}
onChange() {
if (this.ageIsError())
// do something
}
ageIsError() {
return this.myForm.controls.age.hasError('min') &&
this.myForm.controls.age.dirty;
}
单元测试
it('should show error message when age is less than 18', fakeAsync(() => {
let age = component.myForm.controls.age;
age.setValue('10', { emitEvent: true });
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
同样,实际实现在浏览器中有效,但单元测试失败。有谁知道如何在茉莉花中发出事件以将控件设置为脏状态,或者是否有更好的方法来实现这一点?谢谢!
在您的示例中 age.setValue(...)
实际上为输入设置了正确的值,但它不会将 ageError
附加到 DOM - 因为没有 real/emulated将控件标记为脏的事件。这就是为什么在这种情况下方法 ageIsError
总是 returns false
。
作为一种解决方法,我只是使用 document.createEvent('Event')
模拟了输入事件,看起来它工作正常:
it('should show error message when age is less than 18', async(() => {
const customEvent: Event = document.createEvent('Event');
customEvent.initEvent('input', false, false);
const ageInput = fixture.debugElement.query(By.css('input#age')).nativeElement;
ageInput.value = 10;
ageInput.dispatchEvent(customEvent);
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
我还找到了您解决方案的修复方法 - 只需在 detectChanges
:
之前调用 age.markAsDirty()
it('should show error message when age is less than 18', async(() => {
let age = component.myForm.controls.age;
age.setValue('10'); // { emitEvent: true } is by default
age.markAsDirty(); // add this line
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
我也创建了一个stackblitz example,你也看看吧。希望这些解决方案对您有所帮助:)
表单控件绑定会自动更新。
以下代码在测试块中。它只是获取一个输入字段并在其上调度一个更新值。
然后我渲染出控制器状态并且可以确认绑定自动工作。
const usernameInput: HTMLInputElement = fixture.nativeElement.querySelector(
'#username'
);
usernameInput.value = 'testValueNotEmail';
usernameInput.dispatchEvent(new Event('input'));
// FormGroup and Controll Bindings and Validations were automatically updated after the event.
console.log(component.loginForm.getRawValue());
console.log(component.loginForm.get('username')?.errors);
console.log(component.loginForm.get('username')?.touched);
console.log(component.loginForm.get('username')?.dirty);
// In order for the fixture render element changes to take effect,
// I had to detect the changes. The form error fields were then rendered.
fixture.detectChanges();
const errorField = fixture.nativeElement.querySelector(
'#usernameSmall'
);
console.log(errorField);
我有一个更改函数,我正在传递给 反应式表单控件 的更改事件,它评估 脏 状态并检查如果控件上有任何错误,如果是,则将布尔标志设置为 true/false。然后使用此布尔标志来确定是否显示具有错误消息的 <div>
元素。这在浏览器中工作得很好,但是当单元测试运行时,"dirty" 永远不会被设置为 true。这是我的代码:
HTML
<form [formGroup]="myForm" novalidate>
<input id="age" formControlName="age" (change)="onChange()" />
<div id="ageError" *ngIf="ageIsError()">
<label>Age has errored</label>
</div>
</form>
组件
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
age: [null, [Validators.min(18)]]
});
}
onChange() {
if (this.ageIsError())
// do something
}
ageIsError() {
return this.myForm.controls.age.hasError('min') &&
this.myForm.controls.age.dirty;
}
单元测试
it('should show error message when age is less than 18', fakeAsync(() => {
let age = component.myForm.controls.age;
age.setValue('10', { emitEvent: true });
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
同样,实际实现在浏览器中有效,但单元测试失败。有谁知道如何在茉莉花中发出事件以将控件设置为脏状态,或者是否有更好的方法来实现这一点?谢谢!
在您的示例中 age.setValue(...)
实际上为输入设置了正确的值,但它不会将 ageError
附加到 DOM - 因为没有 real/emulated将控件标记为脏的事件。这就是为什么在这种情况下方法 ageIsError
总是 returns false
。
作为一种解决方法,我只是使用 document.createEvent('Event')
模拟了输入事件,看起来它工作正常:
it('should show error message when age is less than 18', async(() => {
const customEvent: Event = document.createEvent('Event');
customEvent.initEvent('input', false, false);
const ageInput = fixture.debugElement.query(By.css('input#age')).nativeElement;
ageInput.value = 10;
ageInput.dispatchEvent(customEvent);
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
我还找到了您解决方案的修复方法 - 只需在 detectChanges
:
age.markAsDirty()
it('should show error message when age is less than 18', async(() => {
let age = component.myForm.controls.age;
age.setValue('10'); // { emitEvent: true } is by default
age.markAsDirty(); // add this line
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
我也创建了一个stackblitz example,你也看看吧。希望这些解决方案对您有所帮助:)
表单控件绑定会自动更新。
以下代码在测试块中。它只是获取一个输入字段并在其上调度一个更新值。
然后我渲染出控制器状态并且可以确认绑定自动工作。
const usernameInput: HTMLInputElement = fixture.nativeElement.querySelector(
'#username'
);
usernameInput.value = 'testValueNotEmail';
usernameInput.dispatchEvent(new Event('input'));
// FormGroup and Controll Bindings and Validations were automatically updated after the event.
console.log(component.loginForm.getRawValue());
console.log(component.loginForm.get('username')?.errors);
console.log(component.loginForm.get('username')?.touched);
console.log(component.loginForm.get('username')?.dirty);
// In order for the fixture render element changes to take effect,
// I had to detect the changes. The form error fields were then rendered.
fixture.detectChanges();
const errorField = fixture.nativeElement.querySelector(
'#usernameSmall'
);
console.log(errorField);