Reputation: 2603
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
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