How to get/set an Ionic ion-input FormControl value within a FormGroup from a Unit Test?


#1

This is cross posted over at StackOverflow: http://stackoverflow.com/questions/41113444/how-to-get-set-an-ionic-2-ion-input-formcontrol-value-within-a-formgroup-from-a

I would like to write a unit test that interacts with an Angular 2/Ionic 2 ion-input field by setting values in the input field and then examining the associated instance members.

Specifically, I would like to:

set a default value in the component instance.
verify that the value is set in the related DOM element.
enter a value into the related DOM element (input field)
verify that it is reflected in the component instance.
I have this working for a normal HTML input field however there is something about the use of an ion-input field that I do understand.

My unit test and test component:

/**
* Form Tests
*/

import {Component} from '@angular/core';

import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing';

import { By }              from '@angular/platform-browser';
import { DebugElement }    from '@angular/core';

import {
  AbstractControl,
  FormControl,
  FormGroup,
  FormsModule,
  ReactiveFormsModule
} from '@angular/forms';

import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';

// Ionic imports

import { 

  App, 
  MenuController, 
  NavController, 
  Platform, 
  Config, 
  Keyboard, 
  Form, 
  IonicModule 

} from 'ionic-angular';

describe( 'Ionic Form Tests', 
  () => {

    // -----------------------------------

    /**
    * instance and element in a FormControl should match, right?
    */

    it( 'nativeElement and instance should match with input and ion-input in a FormGroup', 

      fakeAsync(() => {

        TestBed.configureTestingModule({
          declarations: [
            IonicFormTestComponent
          ],
          providers: [
            App,
            Platform,
            Form,

            { provide: Config, useClass: ConfigMock },

          ],
          imports: [
            FormsModule,
            IonicModule,
            ReactiveFormsModule,
          ],
        });

        let fixture: any = TestBed.createComponent( IonicFormTestComponent );

        fixture.whenStable().then(() => {

          fixture.detectChanges();
          tick();

          let instance = fixture.componentInstance;

          // first check that the initial plain input value and element match

          let plainInputEl = fixture.debugElement.query( By.css( '[formControlName="plainInputControl"]' ) ).nativeElement;

          expect( instance.plainInputControl.value ).toEqual( 'plain input control value' );
          expect( plainInputEl.value ).toEqual( 'plain input control value' );

          // now check to see if the model updates when we update the DOM element

          plainInputEl.value = 'updated Plain Input Control Value';

          dispatchEvent( plainInputEl, 'input' );
          fixture.detectChanges();
          tick();

          // this works

          expect( instance.plainInputControl.value ).toEqual( 'updated Plain Input Control Value' );

          // -------------------------------------------------------------
          // repeat with ion-input

          let ionicInputEl = fixture.debugElement.query( By.css( '[formControlName="ionicInputControl"]' ) ).nativeElement;

          expect( instance.ionicInputControl.value ).toEqual( 'ionic input control value' );

          // this fails with ionicInputEl.value 'undefined' 
          // (how to correctly get the value of the ion-input element?)

          expect( ionicInputEl.value ).toEqual( 'ionic input control value' );

          ionicInputEl.value = 'updated Ionic Input Control Value';

          dispatchEvent( ionicInputEl, 'input' );
          fixture.detectChanges()
          tick();

          console.log( "Ionic input element value is:", ionicInputEl.value );

          // this fails, instance.ionicInputControl.value not changed.

          expect( instance.ionicInputControl.value ).toEqual( 'updated Ionic Input Control Value' );

        });

      }) // end of fakeAsync()

    ); // end of it()

  }

); // end of describe()

// -------------------------------------------------

/**
* ionic test component with form Group 
*/

@Component({
  selector: 'ionic-form-test-component',
  template: `
    <form [formGroup]="testFormGroup">
      <input type="text" value="" formControlName="plainInputControl" />
      <ion-input type="text" value="" formControlName="ionicInputControl"></ion-input>
    </form>
   `
})

export class IonicFormTestComponent {

  testFormGroup: FormGroup;
  plainInputControl: AbstractControl;
  ionicInputControl: AbstractControl;


  constructor() {

    this.testFormGroup = new FormGroup({
        'plainInputControl': new FormControl( '' ),
        'ionicInputControl': new FormControl( '' )
    });

    this.plainInputControl = this.testFormGroup.controls[ 'plainInputControl' ];
    this.plainInputControl.setValue( 'plain input control value' );

    this.ionicInputControl = this.testFormGroup.controls[ 'ionicInputControl' ];
    this.ionicInputControl.setValue( 'ionic input control value' );

  }

}

// --------------------------------------------------

export class ConfigMock {

  public get(): any {
    return '';
  }

  public getBoolean(): boolean {
    return true;
  }

  public getNumber(): number {
    return 1;
  }
}

// END

How do I get/set the value of an ion-input field programmatically within a unit test so that the above works?

Clearly I’m missing something. The Ionic 2 documentation is woefully silent on this subject. The Angular 2 documentation on FormControl seems to imply that it automatically supports two way binding (the successful unit test above seems to support that assertion.)


#2

Ok, it looks like to update the element in a way that Ionic will notice you have to change the value of the first input child of ion-input.

let ionicInputEl = fixture.debugElement.query( By.css( '[formControlName="ionicInputControl"]' ) ).nativeElement;
ionicInputEl.children[0].value = "some value"

This seems awkward and code the relies on it I would imagine risks breaking if the internal working of ion-input changes.

I’ve filed an issue about this: https://github.com/driftyco/ionic/issues/9622


#3

Good morning, a question like configures to ionic 2 for unit test, I could not, you have an example to be able to guide me


#4

Take a look at my stackoverflow question.


#5

Yes , but how do you config your karma.config?


#6

Still being ugly…

I’m facing the same problem and seems like people doesn’t care too much about unit testing… Perhaps you (@Yermo) should improve the subject of this threat in order to get help…


#7

Take a look at my comment in your bug report.

This is the solution I reach, not as ugly as the last you suggest:

fixture.detectChanges (); // Renders the component before querying.
let ionicInputEl = fixture.debugElement.query( By.css( '[formControlName="ionicInputControl"] > input' ) ).nativeElement;
ionicInputEl.value = "some value"

Hope it helps you!