I have a change function I am passing to the change event for a reactive form control that evaluates the dirty state and to check if there are any errors on the control and if so then set a boolean flag to true/false. This boolean flag is then used to determine whether or not to show a <div>
element that has an error message. This works just fine in the browser, but when the unit test runs, the "dirty" is never being set to true. Here is my code:
HTML
<form [formGroup]="myForm" novalidate>
<input id="age" formControlName="age" (change)="onChange()" />
<div id="ageError" *ngIf="ageIsError()">
<label>Age has errored</label>
</div>
</form>
Component
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;
}
Unit Test
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');
});
}));
Again, the actual implementation works in the browser, but the unit test fails. Does anyone know hoe to emit the event in jasmine to set the control to a dirty state, or is there a better way to achieve this? Thanks!
So, when testing reactive Angular forms we can do our job well by focusing on two things. Testing data validation. We want to make sure the data in the form has to be valid before the form can be submitted. Making sure our form data is being sent to the proper place.
The StatusChanges is an event raised by the Angular forms whenever the Angular calculates the validation status of the FormControl, FormGroup or FormArray. It returns an observable so that you can subscribe to it. The observable gets the latest status of the control. The Angular runs the validation check on every change made to the control.
In angular you can do like this: <input type="text" (keyup)="myFunction (this)"> you can use (keyup)="yourFunction ()" event. Use keyUp event for input element. Changing the value programatically does not trigger the change event. You can create your own Event using the Event API.
Developer and author at DigitalOcean. Reactive form instances like FormGroup and FormControl have a valueChanges method that returns an observable that emits the latest values. You can therefore subscribe to valueChanges to update instance variables or perform operations. Take a look at our intro to Reactive Forms if this is all new to you.
In your example age.setValue(...)
actually sets correct value to the input, but it doesn't append ageError
to the DOM - BECAUSE there wasn't real/emulated event to mark the control as dirty. This is why the method ageIsError
always returns false
in this case.
As a workaround I just emulated input event using document.createEvent('Event')
and seems like it works fine:
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');
});
}));
I also found the fix to your solution - just call age.markAsDirty()
before detectChanges
:
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');
});
}));
I've also created a stackblitz example, please check it out as well. Hope these solutions will be helpful for you :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With