Fork me on GitHub

Arturo Volpe

I am a software engineer. Interested in open source, open data and devops.

Test your components in Ionic 4

07 Dec 2018 » ionic

Ionic 4 is the next version of the Ionic framework, currently is in beta, but it works well, one of the major differences between this release and the previous is the dependency with Angular.

In Ionic 4 Angular is provided as an option (currently with Vue) and when you use the Ionic cli, all the commands use ‘angular-cli’ in the backgrounds and make little modifications to the code they generate.

In this post I will show you how to make a simple test that mocks the user interaction with a form and how to setup and execute all tests.

First, we start creating a simple new project

npm install -g ionic
ionic start simpleProject tabas

When the cli ask if we want to use Ionic 4 press ‘y’.

This project already has some test, but they are very basic, if we want to try our pages with Ionic components, we need some modifications first.

First, run the test

To run the unit test, we simple run:

npm run test

And a chrome window will apear and it will run all tests, you will se something like this in your console:

...
INFO [Chrome 70.0.3538 (Mac OS X)]: Connected on socket 4v72_3NckUMqwCHLAAAA with id 67941402
Chrome 70.0.3538 (Mac OS X 10.14.1): Executed 6 of 6 SUCCESS (0.22 secs / 0.198 secs)
TOTAL: 6 SUCCESS
TOTAL: 6 SUCCESS

Everything works fine out of the box.

Second, add some ionic components

In the component tab1.page.ts we will add an input text and a button, when we press the button, a Alert will show saying hello ${name}

// tab1.page.ts
import {Component} from '@angular/core';
import {AlertController} from '@ionic/angular';

@Component({
    selector: 'app-tab1',
    templateUrl: 'tab1.page.html',
    styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
    public name: string;

    constructor(public alertController: AlertController) {
    }

    async sayHello() {
        const alert = await this.alertController.create({
            header: 'Test',
            subHeader: `Hola ${this.name}`
        });
        await alert.present();
    }
}
<!-- tab1.page.html -->
<ion-content>
    <ion-list>
        <ion-item>
            <ion-input [(ngModel)]="name" id="name" placeholder="Hola"></ion-input>
        </ion-item>
        <ion-item>
            <ion-button (click)="sayHello()">Click</ion-button>
        </ion-item>

    </ion-list>
</ion-content>

This is a very simple component, we insert some text, and press the button and an alert apear.

Third, the test

If we run our test, we will see that there is one failing, the test of the tab1.page.ts, this is because we don’t provide a AlertController provider, and angular can’t perform the dependency injection in our component.

We can fix this in our TestBed configuration, simple add:

    //  tab1.page.spec.ts
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [Tab1Page],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],
            providers: [{
                provide: AlertController, useValue: null
            }]
        }).compileComponents();
    }));

If we check our test output, it will say now that all test are passing, now we can make real tests.

Let’s make a simple test that ensure that every change to our input is reflected on our component.

We will use a little helper from the angular test suite called fakeAsync, this helper should wrap the entire test, and it provides a simple function tick() to simulate time.

    //  tab1.page.spec.ts
    it('should update value', fakeAsync(() => {
        const userInput: Input = fixture.nativeElement.querySelector('#name');

        userInput.value = 'html.userName';

        tick(1000);

        expect(component.name).toBe('html.userName');
    }));

This is a simple test, first we search for the ion-input, we update the value, and then check if the component has the same value, it’s simple enough, too bad it doesn’t work.

To be able to test Ionic components, we need to add the IonicModule and another dependencies, we change the test to be like this:

    //  tab1.page.spec.ts
    beforeEach(async(() => {
        TestBed.configureTestingModule({
            declarations: [Tab1Page],
            schemas: [CUSTOM_ELEMENTS_SCHEMA],
            imports: [
                FormsModule,
                CommonModule,
                BrowserModule,
                IonicModule.forRoot({
                    _testing: true
                }),
            ],

            providers: [
                {provide: AlertController, useValue: null},
                {provide: NavController, useValue: null}
            ]
        }).compileComponents();
    }));

    beforeEach(async () => {
        fixture = TestBed.createComponent(Tab1Page);
        component = fixture.componentInstance;
        fixture.detectChanges();
        await fixture.whenRenderingDone();
        await fixture.whenStable();
    });
    //  tab1.page.spec.ts
    ...
            providers: [
                {provide: AlertController, useValue: new MockAlertController()},
                {provide: NavController, useValue: null}
            ]

   ...

In the first beforeEach we add some modules that are used in our code to make the [(ngModel)] works, we add the IonicModule, and, because in some part the navController is injected in that module, we need to provide a value for that controller.

In the second beforeEach we change the code to wait for ionic to load all the components, and only proceed when everything is stable.

If we check now our test console, everything should be fine.

Now it’s time to test the alert, to do this I will provide you with a simple MockAlertController class that will help us in our testing process, you can get it here ionicMocks, with this, we can simply add to our test:

    //  tab1.page.spec.ts
    ...
        component.sayHello();


        tick(1000);

        const mockAlertController = component.alertController as any as MockAlertController;

        expect(mockAlertController.getLast().visible).toBeTruthy();
        expect(mockAlertController.getLast().header).toBe('Test');
        expect(mockAlertController.getLast().subHeader).toBe('Hola html.userName');

This works great! Let me know if you have an issue!.