Over a million developers have joined DZone.

Testing AngularJS Applications

· Java Zone

Discover how powerful static code analysis and ergonomic design make development not only productive but also an enjoyable experience, brought to you in partnership with JetBrains

This article is the second in a series about learning AngularJS. It describes how to test a simple AngularJS application. In a previous article, Getting Started with AngularJS, I showed how to develop a simple search and edit feature.

What you'll learn

You'll learn to use Jasmine for unit testing controllers and Protractor for integration testing. Angular's documentation has a good developer's guide to unit testing if you'd like more information on testing and why it's important.

The best reason for writing tests is to automate your testing. Without tests, you'll likely be testing manually. This manual testing will take longer and longer as your codebase grows.

What you'll need

  • About 15-30 minutes
  • A favorite text editor or IDE. We recommend IntelliJ IDEA.
  • Git installed.
  • Node.js and NPM installed.

Get the tutorial project

Clone the angular-tutorial repository using git and install the dependencies.

git clone https://github.com/mraible/angular-tutorial.git
cd angular-tutorial
npm install

If you haven't completed the Getting Started with AngularJS tutorial, you should peruse it so you understand how this application works. You can also simply start the app with "npm start" and view it in your browser at http://localhost:8000/app/.

Test the SearchController

  1. Create app/search/search_test.js and populate it with the basic test infrastructure. This code sets up a mock SearchService that has the first function we want to test: query(term). It uses $provide to override the default SearchService. Angular unit-test controllers - mocking service inside controller was a useful question on Stack Overflow for figuring out how to mock services in controllers.

    'use strict';
    describe('myApp.search module', function() {
        var mockSearchService;
        beforeEach(module('myApp.search', function($provide) {
            mockSearchService = {query: function(term) {}};
            $provide.value("SearchService", mockSearchService);
  2. Modify karma.conf.js (in the root directory) to add the search implementation and test.

    files : [
  3. Add the first test to search_test.js. This test verifies that setting a search term and executing the search() function will call the service and return results. You can see the results returned from the service are mocked with deferred.resolve(). The deferred.resolve() call is how to handle promises in unit tests. Related: Introduction to Unit Test: Spies is a good introduction to using spies in unit tests.

    describe('search by term', function() {
        var scope, rootScope, controller, deferred;
        // setup the controller with dependencies.
        beforeEach(inject(function($rootScope, $controller, $q) {
            rootScope = $rootScope;
            scope = $rootScope.$new();
            controller = $controller('SearchController', {$scope: scope, SearchService: mockSearchService });
            deferred = $q.defer();
        it('should search when a term is set and search() is called', function() {
            spyOn(mockSearchService, 'query').andReturn(deferred.promise);
            scope.term = 'M';
            deferred.resolve({data: {name: "Peyton Manning"}});
            expect(scope.searchResults).toEqual({name: "Peyton Manning"});
  4. Run the following command on the command line to start the Karma test runner. You can leave this process running and new tests will be run automatically. You can change the mocked data and expectation to see your test fail.

    npm test
    Running Tests from IntelliJ IDEA
    See Running Unit Tests on Karma to learn how to run your tests from IntelliJ IDEA.
  5. Add a test to verify a search occurs automatically when the term is in the URL. Notice how the code structure had to change a bit to handle $routeParams.

    describe('search by term automatically', function() {
        var scope, rootScope, controller, location, deferred;
        beforeEach(inject(function($rootScope, $controller, $q) {
            rootScope = $rootScope;
            scope = $rootScope.$new();
            // in this case, expectations need to be setup before controller is initialized
            var routeParams = {"term": "peyton"};
            deferred = $q.defer();
            spyOn(mockSearchService, 'query').andReturn(deferred.promise);
            deferred.resolve({data: {name: "Peyton Manning"}});
            controller = $controller('SearchController', {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService });
        it('should search automatically when a term is on the URL', function() {
            expect(scope.searchResults).toEqual({name: "Peyton Manning"});
  6. Add a test to verify the EditController works as expected.

    describe('edit person', function() {
        var scope, rootScope, controller, location, deferred;
        beforeEach(inject(function($rootScope, $controller, $q) {
            rootScope = $rootScope;
            scope = $rootScope.$new();
            // expectations need to be setup before controller is initialized
            var routeParams = {"id": "1"};
            deferred = $q.defer();
            spyOn(mockSearchService, 'fetch').andReturn(deferred.promise);
            deferred.resolve({data: {name: "Peyton Manning", address: {street: "12345 Blake Street", city: "Denver"}}});
            controller = $controller('EditController', {$scope: scope, $routeParams: routeParams, SearchService: mockSearchService });
        it('should fetch a single record', function() {
            expect(scope.person.name).toBe("Peyton Manning");
            expect(scope.person.address.street).toBe("12345 Blake Street");

    After adding this test, you'll likely see the following error in your terminal.

    Chrome 40.0.2214 (Mac OS X 10.10.2) myApp.search module edit person should fetch a single record FAILED
        fetch() method does not exist
        TypeError: Cannot read property 'name' of undefined

    This happens because the mockSearchService does not have a fetch method defined. Modify the beforeEach() on line 7 to add this function.

    mockSearchService = {query: function(term) {}, fetch: function(id) {}};

Extra Credit

Create a test for saving a person. Here's a question on Stack Overflow that might help you verify the location after a save has been performed.

Test the Search Feature

To test if the application works end-to-end, you can write scenarios with Protractor. These are also known as integration tests, as they test the integration between all layers of your application.

To verify end-to-end tests work in the project before you begin, run the following command in one terminal window:

npm start

Then in another window, run the following to execute the tests:

npm run protractor

1. Write your first integration test to verify you can navigate to /search and enter a search term to see results. Add the following to e2e-tests/scenarios.js:

describe('search', function() {
  var searchTerm = element(by.model('term'));
  var searchButton = element(by.id('search'));
  beforeEach(function() {
  it('should allow searching at /search', function() {
    searchButton.click().then(function() {
      expect(element.all(by.repeater('person in searchResults')).count()).toEqual(3);

The "searchTerm" variable represents the input field. The by.model() syntax binds to the "ng-model" attribute you defined in the HTML.

2. Run "npm run protractor". This should fail with the following error.

[launcher] Running 1 instances of WebDriver
Selenium standalone server started at
  1) my app search should allow searching at /search
     NoSuchElementError: No element found using locator: By.id("search")

3. To fix, you need to add an "id" attribute to the Search button in app/search/index.html.

<form ng-submit="search()">
    <input type="search" name="search" ng-model="term">
    <button id="search">Search</button>

4. Run the tests again using "npm run protractor". They should all pass this time.

5. Write another test to verify editing a user displays their information.

describe('edit person', function() {
  var name = element(by.model('person.name'));
  var street = element(by.model('person.address.street'));
  var city = element(by.model('person.address.city'));
  beforeEach(function() {
  it('should allow viewing a person', function() {
    // getText() doesn't work with input elements, see the following for more information:
    // https://github.com/angular/protractor/blob/master/docs/faq.md#the-result-of-gettext-from-an-input-element-is-always-empty
    expect(name.getAttribute('value')).toEqual("Peyton Manning");
    expect(street.getAttribute('value')).toEqual("1234 Main Street");
    expect(city.getAttribute('value')).toEqual("Greenwood Village");

Verify it works with "npm run protractor".

6. Finally, write a test to verify you can save a person and their details are updated. Figuring out how to verify the URL after it changed was assisted by this issue.

describe('save person', function() {
  var name = element(by.model('person.name'));
  var save = element(by.id('save'));
  beforeEach(function() {
  it('should allow updating a name', function() {
    name.sendKeys(" Updated");
    save.click().then(function() {
      // verify url set back to search results
      browser.driver.wait(function() {
        return browser.driver.getCurrentUrl().then(function(url) {
          return url;
      // verify one element matched this change
      expect(element.all(by.repeater('person in searchResults')).count()).toEqual(1);

7. When you run this test with "npm run protractor", it should fail because there's no element with id="save" in app/search/edit.html. Add it to the Save button in this file and try again. You should see something similar to the following:

Finished in 4.478 seconds
6 tests, 9 assertions, 0 failures

Source Code

A completed project with this code in it is available on GitHub at https://github.com/mraible/angular-tutorial on the testing branch.

There are two commits that make the changes for the two main steps in this tutorial:


I hope you've enjoyed this quick-and-easy tutorial on testing AngularJS applications. The first couple AngularJS applications I developed didn't have tests and required a lot of manual testing to verify their quality. After learning that testing AngularJS apps is pretty easy, I now do it on all my projects. Hopefully this tutorial motivates you to do do the same.

Learn more about Kotlin, a new programming language designed to solve problems that software developers face every day brought to you in partnership with JetBrains.


Published at DZone with permission of Matt Raible, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}