If you've done much reading about angularjs you've no doubt come across mention of karma, a test runner recommended especially for use with angularjs applications. The angular-seed project is a great way to get started with the basics of angular testing using karma, but for projects of any significant size you will soon hit the cieling in terms of organizational complexity. What I want to share in this article is the approach I have taken using Grunt and the grunt-karma plugin to sustainably manage my projects' client side unit tests and run them via TravisCI. I plan to write another entry about how to approach the actual minutia of unit testing angular code in the near future.
Karma is really nothing more than a set of centralized configuration that builds a test runner for you. The advantage being that it allows you to easily execute tests in a headless browser, and output to the command line. As someone who has actually set all of that up from scratch before, I can assure you the time spent learning how to configure karma, is well worth it. The karma team has done a superb job of making test execution an easy and fast task, and it's all abstracted away from your concern, freeing you up to concentrate on what's really important; testing application code.
Karma can be installed with node.js, it's as simple as npm install karma
. Go look at the docs here
karma.conf.js
#!javascript
module.exports = function(config){
config.set({
basePath : '../',
A good place to put your karma config is in a config/
folder in the project. So this basePath
option tells the config where to begin it's paths for loading from the root.
#!javascript
files : [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'src/**/*.js
'test/unit/**/*.js'
],
The files
array is a listing of files necessary for running your unit tests. In this example angular and a very useful test helper that angular provides called angular-mocks are installed via bower. My files to test are within a directory named src
. The glob format src/**/*.js
tells karma to recursively traverse all directories within the src
directory and to load all files with a .js
extension into the resultant karma test runner. Same goes under the test/unit
directory.
A word of caution. It can be very necessary that your source files are loaded in a particular order because of their dependencies, in that case you should list them out in the correct order. This has bitten me more than once. If you're getting $injector[nomod]
errors this is likely the cause.
#!javascript
autoWatch : false,
This is set to false, because as you'll see shortly. I use grunt-contrib-watch to manage this for me instead. The karma autowatch directive is useful when running karma from a shell command, but not so much with grunt.
#!javascript
frameworks: ['jasmine'],
browsers : ['Chrome'],
There are other test frameworks available with karma, but angular-mocks and angular-scenarios are testing extensions which are very useful for testing angular, and they work specifically with jasmine. Very matter of factly my explanation will just glance the obvious browsers
line by saying: Chrome is a good browser.
#!javascript
plugins : [
'karma-junit-reporter',
'karma-chrome-launcher',
'karma-firefox-launcher',
'karma-phantomjs-launcher',
'karma-jasmine'
],
Plugins that come with karma. That karma-phantomjs-launcher
is going to be a crucial piece when we go to run our travisci server below, so make sure you have it.
#!javascript
junitReporter : {
outputFile: 'test_out/unit.xml',
suite: 'unit'
}
})}
jUnit output file configuration. To be perfectly honest, I don't actually know what this does and I should probably look into it. If you want to comment below and share your knowledge with any other readers, please do.
Install grunt, grunt-karma, and grunt-contrib-watch.
#!bash
$ npm install -g grunt-cli
$ npm install grunt-karma grunt-contrib-watch
Gruntfile.js
#!javascript
module.exports = function(grunt) {
grunt.initConfig({
karma: {
unit: {
configFile: 'config/karma.conf.js',
background: true
}
},
watch: {
karma: {
files: ['src/**/*.js', 'test/unit/**/*.js'],
tasks: ['karma:unit:run']
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('devmode', ['karma:unit', 'watch']);
};
Now that there is a registered task for running the tests and starting up a watch process to run them any time a .js
is saved, we can run $ grunt devmode
from the terminal and keep track of our test output.
It's that simple. Point the karma plugin at karma.conf.js, and setup a watch task and you're ready for the red, green, refactor loop of angular tdd. Now all you have to figure out is how to actually write unit tests.
I stole these from angular-seed because they're simple and they'll get the job done.
src/version.js
#!javascript
angular.module('myApp.services', []).value('version', '0.1');
test/unit/versionSpec.js
#!javascript
describe('version', function() {
beforeEach(module('myApp.services'));
it('should return current version', inject(function(version) {
expect(version).toEqual('0.1');
}));
});
If you haven't used travis ci, you're in for a treat. It feels efficient to be able to see a big green or red alert square in a github pull request which like a digital Paul Revere is letting you know when the enemy is afoot. Instant, automated, information about your code -- gotta love it. Here's how to get started.
Above, I have shown you how to configure karma tests to run in a convenient devmode
task. To properly run on travis you need to make a few additions to the grunt setup so the tests will run once via phantomjs, and not under a watch task. Karma again has us covered in this regard.
Gruntfile.js
#!javascript
module.exports = function(grunt) {
grunt.initConfig({
karma: {
unit: {
// ...
},
// Add a new travis ci karma configuration
// configs here override those in our existing karma.conf.js
travis: {
configFile: 'config/karma.conf.js',
singleRun: true,
browsers: ['PhantomJS']
}
},
watch: {
// ...
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-karma');
grunt.registerTask('devmode', ['karma:unit', 'watch']);
// Add a new task for travis
grunt.registerTask('test', ['karma:travis'])
};
After you have signed up for travis, and turned on the github service hook for your repo as described in the getting started link above, you're ready to make a travis file.
.travis.yml
language: node_js
node_js:
- '0.10'
before_script:
- 'npm install -g grunt-cli'
For node projects with a package.json
file, travis will automatically run npm install
to setup all dependencies, and npm test
. Configure npm's test script as below so it maps to the grunt test
task configured above. The before_script
declaration ensures that when the server attempts running grunt test
it will indeed already have grunt installed.
package.json
#!javascript
{
"name": "myproject",
"private": true,
"scripts": {
"test": "grunt test"
},
"devDependencies": {...}
}
Your first run on travis has to be triggered by a git push, so add and save your changes and make a push. Pro tip: go watch travis run live, it's pretty cool. You can also replay those test runs over again later, travis stores them for you as part of the repository's "build history".
README.md
One of the coolest things about travis is that you can share the results of your latest ci run with any consumers or colleagues via your github readme, and it's easy to do. You just add a link to your README.md.
[](https://travis-ci.org/username/reponame)
I hope this helps to ease any difficulty you may have with setting up your angular project with grunt and karma. Until next time, code well.