Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nackjicholson/1cbd083fd74839ed4dfe to your computer and use it in GitHub Desktop.
Save nackjicholson/1cbd083fd74839ed4dfe to your computer and use it in GitHub Desktop.
Unit Testing with AngularJs, Grunt, Karma, and TravisCI

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, configuration for tests

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.

Grunt and grunt-karma

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.

Let's get one stupid test in

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');
  }));
});

Top it off with travisci

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.

[![Build Status](https://secure.travis-ci.org/username/reponame.png?branch=master)](https://travis-ci.org/username/reponame)

Thanks for reading

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.

@simonmorley
Copy link

Helpful, thanks.

@roylee0704
Copy link

superb and easy to understand write-ups, especially for a beginner like me to grasp, now I can picture the whole flow. Thank you so much for spending time writing this.

@mdvanes
Copy link

mdvanes commented Nov 7, 2015

Informative and bonus points for "like a digital Paul Revere".

I'm personally struggling with the concept of isolating test cases. With this I mean when testing a Controller, mocking all it's dependencies and when testing another Controller or e.g. a Directive mocking all of it's dependencies. Apparently, the general way of testing is to include all sources and unit tests at once. This seems like impure tests, but on the other hand, completely isolated tests would require an enormous amount of mocks to keep up-to-date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment