AngularJS Unit Testing with Karma & Jasmine
โก Smart Summary
AngularJS Unit Testing with Karma and Jasmine verifies controllers, directives, and services in isolation. Karma runs specifications inside real browsers, Jasmine supplies the assertion syntax, and angular-mocks injects dependencies without a live server.

One of the most brilliant features of AngularJS is the Testing aspect. When the developers at Google developed AngularJS, they kept testing in mind and made sure that the entire AngularJS framework was testable.
In AngularJS, testing is normally carried out using Karma (framework). AngularJS testing can be carried out without Karma, but the Karma framework has such a brilliant functionality for testing AngularJS code, that it makes sense to use this framework.
- In AngularJS, we can perform Unit Testing separately for controllers and directives.
- We can also perform end to end testing of AngularJS, which is testing from a user perspective.
โ ๏ธ Version note: AngularJS (version 1.x) reached official end of life on 31 December 2021, and Protractor was retired in August 2023. The workflow on this page remains valid for maintaining existing AngularJS 1.x applications. New projects should use modern Angular with a currently supported runner.
Introduction & Installation of Karma framework
Karma is a testing automation tool created by the AngularJS team at Google. The first step for using Karma is to install Karma. Karma is installed via npm (which is a package manager used for easy installation of modules on a local machine).
Installation of Karma
The installation of Karma via npm is done in a two steps process.
Step 1) Execute the below line from within the command line
npm install karma karma-chrome-launcher karma-jasmine --save-dev
Wherein,
- npm is the command line utility for the node package manager used for installing custom modules on any machine. It ships with Node.js.
- The install parameter instructs the npm command line utility that installation is required.
- There are 3 libraries being specified in the command line that are required to work with karma.
- karma is the core library which will be used for testing purposes.
- karma-chrome-launcher is a separate library which enables karma commands to be recognized by the chrome browser.
- karma-jasmine โ This installs jasmine which is a dependent framework for Karma.
Step 2) The next step is to install the karma command line utility. This is required for executing karma line commands. The karma line utility will be used to initialize the karma environment for testing.
To install the command line utility execute the below line from within the command line
npm install -g karma-cli
- karma-cli is used to install the command line interface for karma which will be used to write the karma commands in the command line interface. The -g flag installs it globally so that the karma command is available from any folder.
Configuration of the Karma framework
The next step is to configure karma which can be done via the command
karma init
After the above step is executed, karma will create a karma.conf.js file. The file will probably look like the snippet shown below
files: [ 'node_modules/angular/angular.js', 'node_modules/angular-mocks/angular-mocks.js', 'lib/app.js', 'tests/*.js' ]
The above configuration files tell the karma runtime engine the following things
- angular.js โ This tells karma that your application depends on the core modules in AngularJS.
- angular-mocks.js โ This tells karma to use the Unit Testing functionality for AngularJS from the angular-mocks.js file.
- All of the main application or business logic files are present in the lib folder of your application.
- The tests folder will contain all of the unit tests.
๐ก Tip: Order matters inside the files array. angular.js must load before angular-mocks.js, and both must load before your application code, otherwise the injector is not available when the specifications run.
To check if karma is working, create a file called Sample.js, put in the below code and place it in the test directory.
describe('Sample test', function() { it('Condition is true', function() { expect('AngularJS').toBe('AngularJS'); }); });
The above code has the following aspects
- The describe function is used to give a description of the test. In our case, we are giving the description ‘Sample test’ to our test.
- The ‘it’ function is used to give a name to the test. In our case, we are giving the name of our test as ‘Condition is true’. The name of the test needs to be meaningful.
- The combination of the ‘expect’ and ‘toBe’ keyword states on what is the expected and actual value of the test result. If the actual and expected value is the same, then the test will pass else it will fail.
When you execute the following line at the command prompt, it will execute the above test file
karma start
The below output is taken from the IDE Webstorm in which the above steps were carried out.
- The output comes in the Karma explorer within Webstorm. This window shows the execution of all tests which are defined in the karma framework.
- Here you can see that the description of the test executed is shown which is “Sample test”.
- Next, you can see that the test itself which has a name of “Condition is true” is executed.
- Note that since all tests have the green “Ok” icon next to it which symbolizes that all tests passed.
What is Jasmine in AngularJS Unit Testing?
Karma decides where tests run, while Jasmine decides how they are written. Jasmine is a behaviour driven specification framework that supplies the vocabulary used in every example on this page, which is why the karma-jasmine adapter is installed alongside the runner.
Jasmine contributes four building blocks:
- describe(): Groups related specifications into a suite and accepts a readable description of the unit under test.
- it(): Declares a single specification. The name should state the behaviour being verified rather than the method being called.
- expect() with matchers: Compares an actual value against an expected one. Common matchers include toBe, toEqual, toContain, and toBeDefined.
- beforeEach() and afterEach(): Run setup and teardown before and after every specification, which keeps each test independent.
Jasmine also provides spies. A spy replaces a real function with a recorded stand in, so a controller can be tested without calling a live HTTP endpoint. Because Jasmine needs no DOM and no assertion library of its own, the combination of Karma and Jasmine remains the lightest way to run AngularJS JavaScript unit tests.
Testing AngularJS Controllers
The karma testing framework also has the functionality to test Controllers end to end. This includes testing of the $scope object which is used within Controllers.
Let’s look at an example of how we can achieve this.
In our example,
We would first need to define a controller. This controller would carry out the below-mentioned steps
- Create an ID variable and assign the value 5 to it.
- Assign the ID variable to the $scope object.
Our test will test the existence of this controller and also test to see if the ID variable of the $scope object is set to 5.
First we need to ensure the following pre-requisite is in place
Install the angular-mocks library via npm. This can be done by executing the below line in the command prompt
npm install angular-mocks --save-dev
Next is to modify the karma.conf.js file to ensure the right files are included for the test. The below segment just shows the files part of the karma.conf.js which needs to be modified
files: ['lib/angular.js', 'lib/angular-mocks.js', 'lib/index.js', 'test/*.js']
- The ‘files’ parameter basically tells Karma all the files that are required in the running of the tests.
- The angular.js and angular-mocks.js file are required to run AngularJS unit tests
- The index.js file is going to contain our code for the controller
- The test folder is going to contain all our AngularJS tests
Below is our AngularJS code which will be stored as a file Index.js in the test folder of our application.
The below code just does the following things
- Create an AngularJS module called sampleApp
- Create a controller called AngularJSController
- Create a variable called ID, give it a value of 5 and assign it to the $scope object
var sampleApp = angular.module('sampleApp', []); sampleApp.controller('AngularJSController', function($scope) { $scope.ID = 5; });
Once the above code is executed successfully, the next step would be to create a Test Case to ensure the code has been written and executed properly.
The code for our test will be as shown below.
The code will be in a separate file called ControllerTest.js, which will be placed in the test folder. The below code just does the following key things
- beforeEach function โ This function is used to load our AngularJS module called ‘sampleApp’ before the test run. Note that this is the name of the module in an index.js file.
- The $controller object is created as a mockup object for the controller “AngularJSController” which is defined in our index.js file. In any sort of Unit Testing, a mock object represents a dummy object which will actually be used for the testing. This mock object will actually simulate the behavior of our controller.
- beforeEach(inject(function(_$controller_) โ This is used to inject the mock object in our test so that it behaves like the actual controller.
- var $scope = {}; This is a mock object being created for the $scope object.
- var controller = $controller(‘AngularJSController’, { $scope: $scope }); – Here we are checking for the existence of a controller named ‘AngularJSController’. In here we are also assigning all variables from our $scope object in our controller in the Index.js file to the $scope object in our test file
- Finally, we are comparing the $scope.ID to 5
describe('AngularJSController', function() { beforeEach(module('sampleApp')); var $controller; beforeEach(inject(function(_$controller_){ $controller = _$controller_; })); describe('$scope.ID', function() { it('Check the scope object', function() { var $scope = {}; var controller = $controller('AngularJSController', { $scope: $scope }); expect($scope.ID).toEqual(5); }); }); });
The above test will run in the karma browser and give the same pass result as was shown in the previous topic.
Testing AngularJS Directives
The karma testing framework also has the functionality to test custom directives. This includes the templateURL’s which are used within custom directives.
Let’s look at an example of how we can achieve this.
In our example, we will first define a custom directive which does the following things
- Create an AngularJS module called sampleApp
- Create a custom directive with the name โ guru99
- Create a function that returns a template with a header tag which displays the text “This is AngularJS Testing.”
var sampleApp = angular.module('sampleApp', []); sampleApp.directive('guru99', function () { return { restrict: 'E', replace: true, template: '<h1>This is AngularJS Testing</h1>' }; });
Once the above code is executed successfully, the next step would be to create a test case to ensure the code has been written and executed properly. The code for our test will be as shown below
The code will be in a separate file called DirectiveTest.js, which will be placed in the test folder. The below code just does the following key things
- beforeEach function โ This function is used to load our AngularJS module called ‘sampleApp’ before the test run.
- The $compile service is used to compile the directive. This service is mandatory and needs to be declared so that AngularJS can use it to compile our custom directive.
- The $rootScope is the primary scope of any AngularJS application. We have seen the $scope object of the controller in earlier chapters. Well, the $scope object is the child object of the $rootScope object. The reason this is declared here is because we are making a change to an actual HTML tag in the DOM via our custom directive. Hence, we need to use the $rootScope service which actually listens or knows when any change happens from within an HTML document.
- var element = $compile(“<guru99></guru99>”)($rootScope) โ This is used to check whether our directive gets injected as it should. The directive is registered with the camel case name guru99, so AngularJS matches the lowercase element <guru99> in the markup. Hence this statement is used to make that check.
- expect(element.html()).toContain(“This is AngularJS Testing”) โ This is used to instruct the expect function that it should find the element(in our case the h1 tag) to contain the innerHTML text of “This is AngularJS Testing”.
describe('Unit testing directives', function() { var $compile, $rootScope; beforeEach(module('sampleApp')); beforeEach(inject(function(_$compile_, _$rootScope_){ $compile = _$compile_; $rootScope = _$rootScope_; })); it('Check the directive', function() { // Compile a piece of HTML containing the directive var element = $compile("<guru99></guru99>")($rootScope); $rootScope.$digest(); expect(element.html()).toContain("This is AngularJS Testing"); }); });
The above test will run in the karma browser and give the same pass result as was shown in the previous topic.
End to End Testing AngularJS applications
The karma testing framework along with a framework called Protractor has the functionality of testing a web application end to end.
So it’s not only testing of directives and controllers, but also testing of anything else which may appear on an HTML page.
Let’s look at an example of how we can achieve this.
In our example below, we are going to have an AngularJS application which creates a data table using the ng-repeat directive.
- We are first creating a variable called “tutorial” and assigning it some key-value pairs in one step. Each key-value pair will be used as data when displaying the table. The tutorial variable is then assigned to the scope object so that it can be accessed from our view.
- For each row of data in the table, we are using the ng-repeat directive. This directive goes through each key-value pair in the tutorial scope object by using the variable ptutor.
- Finally, we are using the <td> tag along with the key value pairs (ptutor.Name and ptutor.Description) to display the table data.
<table> <tr ng-repeat="ptutor in tutorial"> <td>{{ ptutor.Name }}</td> <td>{{ ptutor.Description }}</td> </tr> </table> </div> <script type="text/javascript"> var app = angular.module('DemoApp', []); app.controller('DemoController', function($scope) { $scope.tutorial = [ {Name: "Controllers", Description: "Controllers in action"}, {Name: "Models", Description: "Models and binding data"}, {Name: "Directives", Description: "Flexibility of Directives"} ]; });
Once the above code is executed successfully, the next step would be to create a test case to ensure the code has been written and executed properly. The code for our test will be as shown below
Our test is actually going to test the ng-repeat directive and ensure that it contains 3 rows of data as it should from the above example.
First we need to ensure the following pre-requisite is in place
Install the protractor library via npm. This can be done by executing the below line in the command prompt
npm install -g protractor
The code for our test will be as shown below.
The code will be in a separate file called CompleteTest.js , which will be placed in the test folder. The below code just does the following key things
- The browser function is provided by the protractor library and assumes that our AngularJS application (with the code shown above) is running on our site URL – http://localhost:8080/Guru99/
- var list = element.all(by.repeater(‘ptutor in tutorial’)); -This line of code is actually fetching the ng-repeat directive which is populated by the code ‘ptutor in tutorial’. The element and by.repeater are special keywords provided by the protractor library that allows us to get details of the ng-repeat directive.
- expect(list.count()).toEqual(3); – Lastly, we are using the expect function to see that we are indeed getting 3 items being populated in our table as a result of the ng-repeat directive.
describe('Unit testing end to end', function() { beforeEach(function() { browser.get('http://localhost:8080/Guru99/'); }); it('Check the ng directive', function() { var list = element.all(by.repeater('ptutor in tutorial')); expect(list.count()).toEqual(3); }); });
The above test will run in the karma browser and give the same pass result as was shown in the previous topic.
Karma vs Jasmine vs Protractor: Key Differences
The three tools used across this page are often confused because they appear in the same command line. Each one occupies a different layer of the testing stack, as the table below makes clear.
| Parameter | Karma | Jasmine | Protractor |
| Category | Test runner | Specification framework | End to end automation framework |
| Main job | Launches browsers and reports results | Provides describe, it, and expect | Drives a live application in a real browser |
| Test level | Unit and integration | Unit and integration | End to end |
| Needs a running server | No | No | Yes |
| Configuration file | karma.conf.js | None, it runs inside a runner | protractor.conf.js |
| Current status | Deprecated, maintenance only | Actively maintained | Retired in August 2023 |
In short, Jasmine writes the test, Karma executes it, and Protractor extends the same Jasmine syntax to a fully rendered page.
Best Practices for AngularJS Unit Testing
A small set of habits keeps an AngularJS suite fast and trustworthy as the application grows.
- Test one unit at a time. Load only the module under test inside beforeEach, and mock every collaborator so a failure points at a single file.
- Never call a real backend. Use the $httpBackend service from angular-mocks to return canned responses. Tests then run offline and produce identical results on every machine.
- Keep specification names behavioural. A name such as “returns 5 when the controller initialises” reads better in a report than “test1”.
- Always flush the digest cycle. Call $rootScope.$digest() after changing scope data, otherwise bindings and watchers never update and assertions fail for the wrong reason.
- Measure coverage, but judge it carefully. Add karma-coverage to see untested branches. High coverage with weak assertions still hides defects.
- Run the suite in continuous integration. Use a headless Chrome launcher so the same specifications execute on every commit rather than only on a developer machine.
Applying these rules turns the examples above from isolated demonstrations into a regression safety net for a legacy AngularJS codebase. For the wider picture, see the AngularJS tutorial series.

