Unit Testing Your Ionic Mobile App

Testing, like exercising, is one of those things in life that takes effort, but pays dividends in the long run. Having well-written tests for your application allows you to maintain, extend, and refactor code much more quickly than you would be able to without them. In this post, I will walk you through setting up unit tests for your hybrid mobile app powered by the Ionic / PhoneGap / Cordova framework. We will be using the Karma / Jasmine testing frameworks to run the tests.

Since the Ionic framework is typically built on top of an AngularJS-powered webapp, it is very helpful to have some familiarity with unit testing best-practices for Angular apps. It is not necessarily a straightforward process the first few times you work through it. You can read the official Angular unit testing docs here.

Step 1: Installing Dependencies

To begin, you'll need to make sure you have some dependencies installed:

npm install karma --save-dev  
npm install karma-cli -g  
npm install jasmine --save-dev  
npm install karma-chrome-launcher --save-dev  

You will also need to install the correct version Angular-Mocks via Bower. The version of Angular-Mocks that you install should match the version of Angular that your app is powered by. For example, if you're using Angular v1.2.26 you should install Angular-Mocks v 1.2.26 like so:

bower install angular-mocks#1.2.26  

After you've installed angular-mocks, it will be in the bower-components directory of your application. In order for the app to actually use that package to test, you'll need to move it into the myApp/app/www/lib directory of your project.

Step 2: Setting up Karma

Next, you'll need to set up your karma.conf.js configuration file properly. You can step through a wizard to set this file up automatically by running (in your www/ directory):

karma init  

The end result is that you should have a karma.conf.js file that looks like this:

module.exports = function(config) {  
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],

    // list of files / patterns to load in the browser
    files: [
    //Angular source
    'lib/ionic/js/ionic.bundle.js',
    'lib/angular-mocks/angular-mocks.js',
    'lib/angular-local-storage/dist/angular-local-storage.js',
    'lib/ngCordova/dist/ng-cordova.js',
    'lib/ionic/js/angular-ui/angular-ui-router.js',
    'lib/angular-animate/angular-animate.js',
    'lib/angular-sanitize/angular-sanitize.js',

    //App code
    'js/*.js',
    'js/controllers/*.js',
    'js/services/*.js',

    //Test files
    'test/controllers/*.js'
    ],

    // list of files to exclude
    exclude: [
      'karma.conf.js'
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],

    // web server port
    port: 9876,

    // enable / disable colors in the output (reporters and logs)
    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: false,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['Chrome'],

    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false
  });
};

You will need to modify the above example to make sure you're including all of the source files your application is built with. You will also need to edit the files listed under the comment "App code" and "Test files" to point to the location of your application code and your specs.

Step 3: Writing A Test

This part may be confusing if you're not familiar with unit testing in Karma or Angular. In order to correctly write unit tests we will need to inject some things into our spec.

Please note that I'm testing a controller named "AuthCtrl" in this example. You will need to change the name to match the controller you wish to test. Other Angular services (like factories) can be tested in much the same way. Please note this is the most simple example of a test that I can provide, but if you can get this to run properly, you will have a great building block to build more complicated tests on top of:

describe("AuthCtrl Unit Tests", function () {

    var $scope, ctrl, $timeout, $timeout, $http; //, $location;

    beforeEach(function () {
        module('myApp');

        inject(function ($rootScope, $controller, $q, _$timeout_) {
            $scope = $rootScope.$new();
            $timeout = _$timeout_;

            ctrl = $controller('AppCtrl', {
                $scope: $scope
            });
        });
    });

    it("should have a $scope variable", function() {
        expect($scope).toBeDefined();
    });

});
Step 4: Running Your Tests

To run this test and make sure it passes, navigate to the www/ directory of your app and run:

karma start  

This will open a Chrome browser that will mock your Angular app. To actually run the tests, open a new terminal in the same www/ directory and run:

karma run  

and bam! If you've followed the setup correctly, you will see one passing test in your terminal. Congratulations! Have fun building out the rest of your tests.

Additional Reading