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

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

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

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

Take a look at my stackoverflow question.

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

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…

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!