Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is change event for reactive form control not firing in Jasmine Angular unit test?

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!

like image 674
J-man Avatar asked Aug 15 '18 21:08

J-man


People also ask

How to test reactive angular forms?

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.

What is the statuschanges event in angular forms?

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.

How to change the value of input element programatically in angular?

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.

How do I update an instance variable in a reactive form?

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.


1 Answers

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 :)

like image 86
shohrukh Avatar answered Sep 28 '22 17:09

shohrukh