Unit Testing Ionic Native StatusBar


#1

I’m trying to unit test my home.ts component:

import {Component}     from '@angular/core';
import {Feed} from '../../components/feed/feed';
import {Platform} from "ionic-angular/index";
import {StatusBar} from 'ionic-native';

@Component({
    templateUrl: 'build/pages/home/home.html',
    directives: [Feed]
})
export class Home {
    private platform:Platform;

    constructor(platform:Platform) {
        this.platform = platform;
    }

    ionViewWillEnter() {
        this.platform.ready().then(() => {
            StatusBar.styleBlackOpaque();
        });
    }

    ionViewWillLeave() {
        this.platform.ready().then(() => {
            StatusBar.styleDefault();
        });
    }
}

Here is my home.spec.ts:

import {provide} from '@angular/core';
import {beforeEach, beforeEachProviders, describe, expect, injectAsync, it, setBaseTestProviders, resetBaseTestProviders} from '@angular/core/testing';
import {HTTP_PROVIDERS} from '@angular/http';
import {BROWSER_APP_DYNAMIC_PROVIDERS} from "@angular/platform-browser-dynamic";
import {TEST_BROWSER_STATIC_PLATFORM_PROVIDERS, ADDITIONAL_TEST_BROWSER_PROVIDERS} from '@angular/platform-browser/testing';
import {ComponentFixture, TestComponentBuilder} from '@angular/compiler/testing';
import {Observable} from 'rxjs/Observable';
import {Platform, NavController} from 'ionic-angular/index';
import {NavParams} from 'ionic-angular';
import {StatusBar} from 'ionic-native';

// TODO: this pattern of importing 'of' can probably go away once rxjs is fixed
// https://github.com/ReactiveX/rxjs/issues/1713
import 'rxjs/add/observable/of';

resetBaseTestProviders();
setBaseTestProviders(
    TEST_BROWSER_STATIC_PLATFORM_PROVIDERS,
    [BROWSER_APP_DYNAMIC_PROVIDERS, ADDITIONAL_TEST_BROWSER_PROVIDERS]
);

import {Home} from './home';

class MockPlatform {
    public ready():Promise<any> {
        return Promise.resolve();
    }
}

class MockNavController {

}

describe('Home', () => {
    var homeFixture,
        home,
        homeEl;

    beforeEachProviders(() => [
        HTTP_PROVIDERS
    ]);

    beforeEach(injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) => {
        return tcb
            .overrideProviders(Home, [
                provide(Platform, {
                    useClass: MockPlatform
                }),
                provide(NavController, {
                    useClass: MockNavController
                })
            ])
            .createAsync(Home)
            .then((componentFixture:ComponentFixture<Home>) => {
                homeFixture = componentFixture;
                home = componentFixture.componentInstance;
                homeEl = componentFixture.nativeElement;
            });
    }));

    it('should show a feed', () => {
        var feed = homeEl.querySelector('feed');

        expect(feed).not.toBe(null);
    });
    
    describe(`ionViewWillEnter`, () => {
        it('should style the status bar color to white', () => {
            spyOn(StatusBar, 'styleBlackOpaque');
            home.ionViewWillEnter();
            expect(StatusBar.styleBlackOpaque).toHaveBeenCalled();
        });
    });
    
    describe(`ionViewWillLeave`, () => {
        it('should style the status bar color to black', () => {
            spyOn(StatusBar, 'styleDefault');
            home.ionViewWillLeave();
            expect(StatusBar.styleDefault).toHaveBeenCalled();
        });
    });
});

I’m trying to spy on the StatusBar being called from within the ionViewWillEnter and ionViewWillLeave methods but it’s saying it’s never called:

FAILED TESTS:
  Home
    ionViewWillEnter
      ✖ should style the status bar color to white
        PhantomJS 2.1.1 (Mac OS X 0.0.0)
      Expected spy styleBlackOpaque to have been called.
      /var/folders/ks/cdhbslzn3qn397_f0y84gv100000gn/T/7e82d044a07c1bf484d8838c56210be4.browserify:578:89 <- app/pages/home/home.spec.ts:71:63
      /var/folders/ks/cdhbslzn3qn397_f0y84gv100000gn/T/7e82d044a07c1bf484d8838c56210be4.browserify:32702:28 <- node_modules/@angular/core/testing/testing.js:98:0
      invoke@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:323:34
      run@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:216:50
      /Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:571:61
      invokeTask@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:356:43
      runTask@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:256:58
      drainMicroTaskQueue@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:474:43
      invoke@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:426:41

    ionViewWillLeave
      ✖ should style the status bar color to black if window.cordova exists
        PhantomJS 2.1.1 (Mac OS X 0.0.0)
      Expected spy styleDefault to have been called.
      /var/folders/ks/cdhbslzn3qn397_f0y84gv100000gn/T/7e82d044a07c1bf484d8838c56210be4.browserify:585:85 <- app/pages/home/home.spec.ts:79:59
      /var/folders/ks/cdhbslzn3qn397_f0y84gv100000gn/T/7e82d044a07c1bf484d8838c56210be4.browserify:32702:28 <- node_modules/@angular/core/testing/testing.js:98:0
      invoke@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:323:34
      run@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:216:50
      /Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:571:61
      invokeTask@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:356:43
      runTask@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:256:58
      drainMicroTaskQueue@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:474:43
      invoke@/Users/dwilt/Projects/GJS/gjs-app/node_modules/zone.js/dist/zone.js:426:41

Any ideas?


#2

Did you find a solution yet? I’m facing the same problem.
It seems to me that the promise used in the line below is the problem, because it’s asynchronous.


#3

I didn’t, no. Fucking sucks.


#4

I found this later today.

You need to wrap the test in “fakeAsync” and call tick(), this will advance time by 1ms (I guess).
The code in the platform.ready promise will then be executed.
You can also pass an argument to tick e.g. tick(2000);
Which will advance time 2 seconds, could be usefull if you are using timers in your code.