Testing AngularJS with Jasmine & Karma
Unit testing is a cornerstone of test driven development (TDD). While JavaScript has traditionally used Jasmine for unit testing, newer js frameworks have developed more advanced solutions for testing client side code.
Angular is one of these frameworks. While Jasmine remains the preferred framework for testing Angular apps, the creators of Angular have developed their own test runner called Karma. Karma runs on Node.js and gives developers the ability to run unit tests across multiple browsers and devices.
In this tutorial, we will quickly demonstrate how to use jasmine with karma to test your Angular.js app.
Getting Started
This tutorial assumes that you are familiar with the basics of Angular and Node. We will be creating a very simplistic Angular.js controller for testing purposes. To start, let's install the required dependencies through npm.
npm install karma karma-jasmine jasmine-core angular angular-mocks --save
In the example above, we are installing all of the necessary dependencies for our test runner. This includes karma, the jasmine plugin for karma (karma-jasmine), the jasmine core library (jasmine-core), as well as angular and angular-mocks (more on this later).
Configuring your Angular App for Karma
Now that we have all of the necessary dependencies, it's time to configure our app for the Karam test runner. Similar to npm's npm init, we can run:
karma init
You should now be prompted with a series of questions (similar to running npm init). While this gives you the opportunity to specify browsers, test frequencies, etc. you can ignore this for now by simply hitting return until the prompts stop.
If all went according to plan, you should now have a fully generated karma.conf.js file in your root directory.
Finally, navigate to the root of your project and run:
node_modules/karma/bin/karma start
This should start the Karma server on http://localhost:9876/ (or whichever port the server is running on. Check the console output for the correct address).
Writing a test spec
Now that you're up and running, it's time to start testing your app. In your root directory, add a file called test.spec.js and add the following:
test.spec.js
describe('my controller', function(){
it('runs a test', function(){
expect(0).toEqual(0);
})
})
This is the jasmine framework in a nutshell. Notice how we have defined a describe clause with a nested it clause. This aligns with Jasmine's behavioral driven development (BDD) approach. By keeping things verbose, desired behaviors are clearly defined and our test suite is more easily readable.
Running our spec
Now that we have our test.spec.js file ready to run, we need to configure our karma.conf.js file to include and run our spec. In the karma.conf.js file, you should see something like:
files: [],
singleRun: false
If the singleRun attribute is set to false then the test runner will automatically rerun the test suite each time a file is changed. This makes it very convenient for developers and is recommended.
The files attribute is an empty array. This array simply lists all of the files to run when we call karma start. With that said, we need to add the test spec file we just created so karma knows to run it.
files: ['test.spec.js']
By adding the file's string path to this array, Karma now knows to include our test spec when it runs. NOTE: File paths may differ depending on where you place your files. Be sure to correctly reference the spec's path in your karma config file. Now run node_modules/karma/bin/karma start, navigate to the karma server in your browser, and you should see your spec run in the terminal.
Testing Angular Components
Our test spec is a great example, but it lacks relevance to our Angular app. First of all, expecting '0' to be '0' isn't very revealing to our app's functionality. Also, we want to test actual Angular controllers, services, etc. that we've written.
To do this, we use angular-mocks: a library for mocking up our existing angular code so that we can use them in our spec files. You should already have angular-mocks installed via npm (see previous steps) but you also need to include this in your karma configuration. Head back to your karma.conf.js file and add the following paths:
files: [
node_modules/angular/angular.js',
node_modules/angular-mocks/angular-mocks.js',
controllers/myCtrl.js',
test.spec.js'
]
Here we've included the Angular.js library as well as the angular-mocks module for mocking up our Angular components. Remember that ORDER MATTERS. We must include our Angular libraries before our test spec so we can reference everything.
You may notice that we don't have a controllers/myCtrl.js file yet. While you can reference any controller, service, etc. to test, we will create a simple controller for the sake of this example:
myCtrl.js
angular.module('myModule',[])
.controller('myCtrl', function($scope){
$scope.count = 0
$scope.add = function(){
$scope.count += 1;
}
})
Again, this is just a basic controller for our example's sake. Let's update our test spec to actually test our newly created controller.
test.spec.js
describe('my controller', function(){
beforeEach(angular.mock.module('myModule'));
var $controller
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
describe('$scope', function(){
it('adds one', function(){
var $scope = {};
var controller = $controller('myCtrl', {$scope: $scope});
$scope.add();
expect($scope.count).toEqual(1);
})
})
})
In the above example, we use angular-mock to include our 'myModule' module with beforeEach(angular.mock.module('myModule')). We then inject an Angular controller before each test. Finally, in our describe clause, we initiate a new 'myCtrl' controller and bind it to our $scope variable. Then we can execute our $scope.add() function and test the result with an assertion (our expect clause).
Conclusion
If you've done everything right then your test should already be passing. Remember that we configured our karma.conf.js file to allow continuous runs. As next steps, start exploring further configuration options available for your Karma test runner. Also remember that other built-in Angular services (such as $httpBackend) are good for mocking up http requests and other asynchronous functionality. By using Karma and angular-mock, you can efficiently write unit tests for your Angular app.