Valyrion
Valyrion

Reputation: 2603

How to set up unit testing of cordova app using AngularJS, Jasmine and Karma

I'm trying to set up Karma with Jasmine to unit test my Cordova app using AngularJS, but I can't seem to make it run any tests or connect it in some way to the Android emulator (Genymotion). I've currently set up Karma to run in Chrome and load all the js files + tests of my app, but no outut is shown. All I get is a screen saying Karma was connected. When I tried to just set up some Jasmine tests by itself and inlcluded the test file in my index.html page, the tests ran and inserted some output into the app html, but I'd like to get to a point where I can write the tests and run them without having to start up the actual app every time.

I've tried using the cordova launcher plugin but still nothing is actually happening. It lists all the included files in the console and then just appears to stop entirely (no output window here)

Is it at all possible to set up unit testing this way or am I just doomed to littering my code with console.log statements for eternity?

Upvotes: 2

Views: 3726

Answers (1)

Sten Muchow
Sten Muchow

Reputation: 6701

Setting up unit tests in Karma/Jasmine for an Angular App running using the cli and grunt to do our dirty work.

First off you will need to have grunt installed, but I'll skip these kind of duh statements from now on and just give you a more or less copy of how we are doing our unit testing. Which btw works awesome - I am fully part of the unit test cult - Gooble Gabble Gooble Gabble!

Package.json:

{
  "name": "Kinetix-Store-Frontend",
  "version": "0.0.1",
  "devDependencies": {
    "findup-sync": "^0.1.3",
    "grunt-autoprefixer": "~0.7.3",
    "grunt-bower-install": "~1.4.0",
    "grunt-concurrent": "~0.5.0",
    "grunt-contrib-clean": "~0.5.0",
    "grunt-contrib-compass": "~0.7.2",
    "grunt-contrib-concat": "~0.4.0",
    "grunt-contrib-connect": "~0.7.1",
    "grunt-contrib-copy": "~0.5.0",
    "grunt-contrib-cssmin": "~0.9.0",
    "grunt-contrib-htmlmin": "~0.2.0",
    "grunt-contrib-jshint": "~0.10.0",
    "grunt-contrib-sass": "~0.7.3",
    "grunt-contrib-uglify": "~0.4.0",
    "grunt-contrib-watch": "~0.6.1",
    "grunt-html-snapshot": "~0.6.1",
    "grunt-karma": "~0.8.3",
    "grunt-newer": "~0.7.0",
    "grunt-ng-annotate": "^0.3.0",
    "grunt-rev": "~0.1.0",
    "grunt-svgmin": "~0.4.0",
    "grunt-usemin": "~2.1.1",
    "jasmine-reporters": "~0.4.1",
    "jshint-stylish": "~0.1.5",
    "karma": "~0.12.14",
    "karma-jasmine": "~0.2.2",
    "karma-junit-reporter": "~0.2.2",
    "karma-ng-html2js-preprocessor": "~0.1.0",
    "karma-phantomjs-launcher": "~0.1.4",
    "load-grunt-tasks": "~0.4.0",
    "time-grunt": "~0.3.1"
  },
  "engines": {
    "node": ">=0.8.0"
  },
  "dependencies": {
    "grunt": "^0.4.5"
  }
}

Karma.conf.js

// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html

module.exports = function (config) {
    config.set({
        // base path, that will be used to resolve files and exclude
        basePath: '',

        // testing framework to use (jasmine/mocha/qunit/...)
        frameworks: ['jasmine'],

        preprocessors: {
            'app/views/templates/*.tpl.html': ['ng-html2js']
        },

        // list of files / patterns to load in the browser
        files: [
            'app/bower_components/lodash/dist/lodash.js',
            'app/bower_components/angular/angular.js',
            'app/bower_components/angular-mocks/angular-mocks.js',
            'app/bower_components/angular-resource/angular-resource.js',
            'app/bower_components/angular-cookies/angular-cookies.js',
            'app/bower_components/angular-sanitize/angular-sanitize.js',
            'app/bower_components/angular-bootstrap/ui-bootstrap.js',
            'app/bower_components/angular-ui-router/release/angular-ui-router.js',
            'app/bower_components/angular-local-storage/angular-local-storage.js',
            'app/bower_components/jquery/dist/jquery.js',
            'app/bower_components/bootstrap/dist/js/bootstrap.js',
            'app/bower_components/lodash/dist/lodash.compat.js',
            'app/scripts/*.js',
            'app/scripts/**/*.js',
            'test/spec/**/*.js',

            //Templates
            'app/views/templates/*.tpl.html'
        ],


        ngHtml2JsPreprocessor: {
            stripPrefix: 'app/',
            moduleName: 'Kinetix.Templates'
        },


        // list of files / patterns to exclude
        exclude: [],

        // Reporters
        reporters: ['progress', 'junit'],

        //Config for junit
        junitReporter: {
            outputFile: './test/test-results.xml',
            suite: ''
        },

        // web server port
        port: 9001,

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


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


        // Start these browsers, currently available:
        // - Chrome
        // - ChromeCanary
        // - Firefox
        // - Opera
        // - Safari (only Mac)
        // - PhantomJS
        // - IE (only Windows)
        browsers: ['PhantomJS'],


        // Continuous Integration mode
        // if true, it capture browsers, run tests and exit
        singleRun: false
    });
};

GruntFile.js - ngMin should be ngAnnotate but i am in the process right now of solving this issue. As you can see this was made for us by Yeoman's Angular Generator - HIGHLY! recommend....

// Generated on 2014-01-16 using generator-angular 0.7.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt);

    // Time how long tasks take. Can help when optimizing build times
    require('time-grunt')(grunt);

    // Define the configuration for all the tasks
    grunt.initConfig({

        // Project settings
        yeoman: {
            // configurable paths
            app: require('./bower.json').appPath || 'app',
            dist: 'dist',
            test: 'test'
        },

        configureRewriteRules: {

            options: {
                rulesProvider: 'connect.rules'
            }

        },


        // Watches files for changes and runs tasks based on the changed files
        watch: {
            js: {
                files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
                tasks: ['newer:jshint:all'],
                options: {
                    livereload: true
                }
            },
            jsTest: {
                files: ['test/spec/{,*/}*.js'],
                tasks: ['newer:jshint:test', 'karma']
            },
            styles: {
                files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
                tasks: ['newer:copy:styles', 'autoprefixer']
            },
            gruntfile: {
                files: ['Gruntfile.js']
            },
            sass: {
                files: ['**/*.{scss,sass}'],
                tasks: ['sass:dev']

            }
        },

        // The actual grunt server settings
        connect: {
            options: {
                port: 9000,
                // Change this to '0.0.0.0' to access the server from outside.
                hostname: '0.0.0.0',
                keepalive: true
            },


            livereload: {
                options: {
                    open: true,
                    base: [
                        '<%= yeoman.app %>'
                    ]
                }
            },
            test: {
                options: {
                    port: 9001,
                    base: [
                        '.tmp',
                        'test',
                        '<%= yeoman.app %>'
                    ]
                }
            },
            dist: {
                options: {
                    open: true,
                    base: ['<%= yeoman.dist %>'],
                    keepalive: true
                }
            }



        },

        // Make sure code styles are up to par and there are no obvious mistakes
        jshint: {
            options: {
                jshintrc: '.jshintrc',
                reporter: require('jshint-stylish')
            },
            all: [
                'Gruntfile.js',
                '<%= yeoman.app %>/scripts/{,*/}*.js'
            ],
            test: {
                options: {
                    jshintrc: 'test/.jshintrc'
                },
                src: ['test/spec/{,*/}*.js']
            }
        },

        // Empties folders to start fresh
        clean: {
            dist: {
                files: [
                    {
                        dot: true,
                        src: [
                            '.tmp',
                            '<%= yeoman.dist %>/*',
                            '!<%= yeoman.dist %>/.git*'
                        ]
                    }
                ]
            },
            server: '.tmp'
        },

        // Add vendor prefixed styles
        autoprefixer: {
            options: {
                browsers: ['last 1 version']
            },
            dist: {
                files: [
                    {
                        expand: true,
                        cwd: '.tmp/styles/',
                        src: '{,*/}*.css',
                        dest: '.tmp/styles/'
                    }
                ]
            }
        },

        // Automatically inject Bower components into the app
        'bowerInstall': {
            app: {
                src: ['<%= yeoman.app %>/index.html'],
                ignorePath: '<%= yeoman.app %>/'
            }
        },


        // Renames files for browser caching purposes
        rev: {
            dist: {
                files: {
                    src: [
                        '<%= yeoman.dist %>/scripts/*.js',
                        '<%= yeoman.dist %>/styles/*.css',
                        '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
                        '<%= yeoman.dist %>/styles/fonts/*'
                    ]
                }
            }
        },

        // Reads HTML for usemin blocks to enable smart builds that automatically
        // concat, minify and revision files. Creates configurations in memory so
        // additional tasks can operate on them
        useminPrepare: {
            html: '<%= yeoman.app %>/index.html',
            options: {
                dest: '<%= yeoman.dist %>'
            }
        },

        // Performs rewrites based on rev and the useminPrepare configuration
        usemin: {
            html: ['<%= yeoman.dist %>/{,*/}*.html'],
            css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],

            options: {
                assetsDirs: ['<%= yeoman.dist %>']
            }
        },

        // The following *-min tasks produce minified files in the dist folder
        imagemin: {
            dist: {
                files: [
                    {
                        expand: true,
                        cwd: '<%= yeoman.app %>/images',
                        src: '{,*/}*.{png,jpg,jpeg,gif}',
                        dest: '<%= yeoman.dist %>/images'
                    }
                ]
            }
        },
        svgmin: {
            dist: {
                files: [
                    {
                        expand: true,
                        cwd: '<%= yeoman.app %>/images',
                        src: '{,*/}*.svg',
                        dest: '<%= yeoman.dist %>/images'
                    }
                ]
            }
        },
        htmlmin: {
            dist: {
                options: {
                    collapseWhitespace: true,
                    collapseBooleanAttributes: true,
                    removeCommentsFromCDATA: true,
                    removeOptionalTags: true
                },
                files: [
                    {
                        expand: true,
                        cwd: '<%= yeoman.dist %>',
                        src: ['*.html', 'views/{,*/}*.html'],
                        dest: '<%= yeoman.dist %>'
                    }
                ]
            }
        },



        // Replace With Google CDN references
        cdnify: {
            dist: {
                html: ['<%= yeoman.dist %>/*.html']
            }
        },

        // Copies remaining files to places other tasks can use
        copy: {
            dist: {
                files: [
                    {
                        expand: true,
                        dot: true,
                        cwd: '<%= yeoman.app %>',
                        dest: './.tmp/',
                        src: [
                            '*.{ico,png,txt}',
                            '.htaccess',
                            '*.html',
                            'views/{,*/}{,*/}*.html',
                            'scripts/{,*/}{,*/}{,*/}*.js',
                            'styles/{,*/}{,*/}glyphicons-*',
                            'bower_components/**/*',
                            'images/{,*/}*.{webp}',
                            'fonts/*'
                        ]
                    },
                    {
                        expand: true,
                        cwd: './.tmp/images',
                        dest: '<%= yeoman.dist %>/images',
                        src: ['generated/*']
                    }
                ]
            },
            styles: {
                expand: true,
                cwd: '<%= yeoman.app %>/styles',
                dest: '.tmp/styles/',
                src: '{,*/}*.css'
            }
        },

        ngAnnotate: {
            options: {
                singleQuotes: true
            },
            dist: {
                files: [
                    {
                        expand: true,
                        src: './.tmp/scripts/{,*/}{,*/}{,*/}*.js'
                    }
                ]
            }
        },
        // Run some tasks in parallel to speed up the build process
        concurrent: {
            server: [
                'copy:styles'
            ],
            test: [
                'copy:styles',
                'shell'
            ],
            dist: [
                'copy:styles',
                'imagemin',
                'svgmin'

            ]
        },

        sass: {
            dev: {
                options: {
                    style: 'nested',
                    sourcemap: 'true',
                    'cache-location': '/tmp/sass-cache'
                },
                files: {
                    './app/styles/css/styles.css': './app/styles/sass/modules/styles.scss'
                }
            },
            dist: {
                options: {
                    style: 'compressed',
                    'cache-location': '/tmp/sass-cache'
                },
                files: {
                    './app/styles/css/styles.css': './app/styles/sass/modules/styles.scss'
                }
            }

        },
        // By default, your `index.html`'s <!-- Usemin block --> will take care of
        // minification. These next options are pre-configured if you do not wish
        // to use the Usemin blocks.
        // cssmin: {
        //   dist: {
        //     files: {
        //       '<%= yeoman.dist %>/styles/main.css': [
        //         '.tmp/styles/{,*/}*.css',
        //         '<%= yeoman.app %>/styles/{,*/}*.css'
        //       ]
        //     }
        //   }
        // },
        // uglify: {
        //   dist: {
        //     files: {
        //       '<%= yeoman.dist %>/scripts/scripts.js': [
        //         '<%= yeoman.dist %>/scripts/scripts.js'
        //       ]
        //     }
        //   }
        // },
        // concat: {
        //   dist: {}
        // },

        karma: {
            unit: {
                configFile: './karma.conf.js',
                singleRun: true
            }
        },


        protractor: {
            options: {
                configFile: '<%= yeoman.test %>/protractor.conf.js', // Default config file
                keepAlive: true // If false, the grunt process stops when the test fails.
            },
            e2e: {
                options: {
                    configFile: '<%= yeoman.test %>/protractor.conf.js', // Target-specific config file
                    args: {} // Target-specific arguments
                }
            }
        },

        shell: {
            webdriver: {
                options: {
                    stdout: true
                },

                command: 'phantomjs --webdriver=4445'
            }
        },

        htmlSnapshot: {
            all: {
                options: {
                    //that's the path where the snapshots should be placed
                    //it's empty by default which means they will go into the directory
                    //where your Gruntfile.js is placed
                    snapshotPath: 'snapshots/',
                    //This should be either the base path to your index.html file
                    //or your base URL. Currently the task does not use it's own
                    //webserver. So if your site needs a webserver to be fully
                    //functional configure it here.
                    sitePath: 'http://localhost:9000',
                    //you can choose a prefix for your snapshots
                    //by default it's 'snapshot_'
                    fileNamePrefix: 'sp_',
                    //by default the task waits 500ms before fetching the html.
                    //this is to give the page enough time to to assemble itself.
                    //if your page needs more time, tweak here.
                    msWaitForPages: 1000,
                    //sanitize function to be used for filenames. Converts '#!/' to '_' as default
                    //has a filename argument, must have a return that is a sanitized string
                    sanitize: function (requestUri) {
                        //returns 'index.html' if the url is '/', otherwise a prefix
                        if (/\//.test(requestUri)) {
                            return 'index.html';
                        } else {
                            return requestUri.replace(/\//g, 'prefix-');
                        }
                    },
                    //if you would rather not keep the script tags in the html snapshots
                    //set `removeScripts` to true. It's false by default
                    removeScripts: false,
                    //set `removeLinkTags` to true. It's false by default
                    removeLinkTags: false,
                    //set `removeMetaTags` to true. It's false by default
                    removeMetaTags: false,
                    //Replace arbitrary parts of the html
                    replaceStrings: [
                        {'this': 'will get replaced by this'},
                        {'/old/path/': '/new/path'}
                    ],
                    // allow to add a custom attribute to the body
                    bodyAttr: 'data-prerendered',
                    //here goes the list of all urls that should be fetched
                    urls: [
                        '',
                        '#!/en-gb/showcase'
                    ],
                    // a list of cookies to be put into the phantomjs cookies jar for the visited page
                    cookies: [
                        {'path': '/', 'domain': 'localhost', 'name': 'lang', 'value': 'en-gb'}
                    ]
                }
            }
        }
    });


    grunt.registerTask('serve', function (target) {
        if (target === 'dist') {
            return grunt.task.run(['build', 'connect:dist:keepalive']);
        }

        grunt.task.run([
            'clean:server',
            'bowerInstall',
            'concurrent:server',
            'autoprefixer',
            'connect:livereload'
        ]);
    });

    grunt.registerTask('server', function () {
        grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
        grunt.task.run(['serve']);
    });

    grunt.registerTask('test', [
        'clean:server',
        'concurrent:test',
        'autoprefixer',
        'connect:test',
        'karma'
        //'protractor'
    ]);

    grunt.registerTask('build', [
        'newer:jshint',
        'clean:dist',
        'bowerInstall',
        'sass:dist',
        'useminPrepare',
        'copy:styles',
        //'imagemin', // Removed From Build Process - Bamboo problems TODO: Fix this
        'svgmin',
        'autoprefixer',
        'concat',
        'copy:dist',
        'ngAnnotate',
        //'cdnify',  //Do we need this - if so we need to install it
        'cssmin',
        'uglify',
        'rev',
        'usemin',
        'htmlmin'

    ]);

    grunt.registerTask('bamboo', [
        'clean',
        'newer:jshint',
        'karma'
    ]);

    grunt.registerTask('default', [
        'newer:jshint',
        'test',
        'build'
    ]);
};

Then all you need to do is run from the command line:

$ root-of-your-app-where-Gruntfile-lives/grunt karma

Upvotes: 1

Related Questions