Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using Jasmine and Karma to Write and Run AngularJS Unit Tests

DZone's Guide to

Using Jasmine and Karma to Write and Run AngularJS Unit Tests

How to use jasmine and karma to write unit tests for your AngularJS apps not just within Visual Studio, but any IDE.

· Web Dev Zone
Free Resource

Try RAD Studio for FREE!  It’s the fastest way to develop cross-platform Native Apps with flexible Cloud services and broad IoT connectivity. Start Your Trial Today!

 Note: Although we say this post will show you how to use Visual Studio to write AngularJS applications and unit tests, you can use these methods to configure test environments for any IDE.

To write unit tests I will use jasmine, to run them I’ll use karma, and to create proxies of AngularJS components like filter, controller, and service, I’ll use ng-mock. In this post we will cover the following topics:

  • Setting up the development environment
  • Setting up the test environment for jasmine
  • Setting up the karma test runner environment
  • Writing the unit test for filter, controller, and service

We will use npm to install dependencies and Visual Studio to write code, and we’ll run tests using karma from the command prompt. At the end of the post, we should have tests running as shown in the image below:

So let’s get started!

Step 1

Create an ASP.NET Web Application project in Visual Studio by choosing the Empty template project type.

Even though I am creating an AngularJS project in Visual Studio, you can create project using any IDE of your choice. The only thing you need to keep in mind is that all commands we will run here should be executed inside the root folder of the project.

Step 2

Once the project is created, launch the command prompt as an admin and change the directory to the root folder of the created project folder. In the case of Visual Studio, change the directory to the project folder, not the solution folder.

For example, the folder structure I am using is as follows:

  • AngularJSUnitTestDemo : Solution Folder
  • AngularJSUnitTestDemo : Root folder of project. So, we navigated here.

Step 3

We need to make sure that NodeJS is installed on the machine. If it is not installed, download and install it from https://nodejs.org/en/. We can verify whether NodeJS is installed or not by running the following command:

  • node --version 
  • If we get NodeJS version as an output, then NodeJS is installed on the machine.

    Step 4

    Next, let’s install AngularJS to the project. There are various ways to do so (NuGet package, Bower etc.), however, I am choosing NPM to install AngularJS in the project. To install, run the npm command as shown below:

  •  npm install angular --save
  • Step 5

    Now we are going to use Karma test runner to run tests.  Learn more about Karma here: https://karma-runner.github.io/1.0/index.html Created by the Angular team, Karma is a spec-based test runner. To install Karma, run the command as shown below:

  •  npm install -g karma --save-dev

  • Step 6

    We are going to use Jasmine, behavior driven JavaScript Test framework to write Unit Tests. Learn more about Jasmine here:  http://jasmine.github.io

    We will install Jasmine Core and Jasmine Karma. To install them run the command as shown below:

  • npm install karma-jasmine jasmine-core --save-dev
  • Step 7

    We will use the ngMock module to mock up AngularJS services, modules, controllers, filters and more. To use ngMock, we need to install the angular-mocks library in the project. To do so, run the command as shown below:

  • npm install angular-mocks --save-dev
  • Step 8

    Karma allows us to run tests in multiple browsers. To do so, we need to install different browser plugins. In this example, I’d like to run a test in Chrome, so I’ll go ahead and install a Chrome browser plugin as follows:

  • npm install karma-chrome-launcher --save-dev
  • If you wish, you can install other browser launchers too.

    Step 9

    We can have any folder structure for the project adhering to our business requirements. However, for the purpose of this post, I am going to keep it simple. I will create two folders: app and tests. The app folder will keep all Angular scripts files and the tests folder to keep all the tests. To create these folders,  run the commands as shown below:

    • md app
    • md tests

    Step 10

    This step is only required if you are working in Visual Studio, otherwise you can skip this step. We are going to add all newly installed files and newly created folder in the project. Go back to Visual Studio.

    In this step we have included all of our newly created files and folders in Visual Studio project.

    Step 11

    In this step, we are going to add new files to the project:

    • CalculatorApp.js to write AngularJS code. Add this file in app folder.
    • CalculatorAppTest.js to write unit tests for controller, filters, services written in CalculatorApp.js. Add this file in tests folder.
    • SpecRunner.html to display test results in browser using jasmine
    • Index.html, html page of the application.

    To add files in Visual Studio,

    • Right click on the project/ app folder and add a file called CalculatorApp.js
    • Right click on the project/tests folder and add a file called CalculatorAppTest.js
    • Right click on the project and add an HTML file called SpecRunner.html
    • Right click on the project and add an HTML file called index.html

    In this step, we have added new files to write code, unit test and display test results.

    Step 12

    In this step, we will setup SpecRunner.html. This will render test results in the browser. So, open SpecRunner.html and add the following references.

    Jasmine Test Results
    <meta charset="utf-8" />
        <link href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css" rel="stylesheet" />
        <s cript src="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></s cript>
        <s cript src="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></s cript>
        <s cript src="node_modules/jasmine-core/lib/jasmine-core/boot.js"></s cript>
    
        <s cript src="node_modules/angular/angular.js"></s cript>
        <s cript src="node_modules/angular-mocks/angular-mocks.js"></s cript>
    
        <s cript src="app/CalculatorApp.js">
        <s cript src="tests/CalculatorAppTest.js"></s cript>
    

    Step 13 — Writing Tests for AngularJS Filter

    Finally, we have reached to step in which we will write some tests! Let’s start by writing tests for the AngularJS filter. Add following test to CalculatorAppTest.js.

    describe('Calculator App Tests', function () {
    
        beforeEach(module('MyApp'));
    
        describe('reversestringfiltertest', function () {
    
            var reverse;
            beforeEach(inject(function ($filter) { //initialize filter
                reverse = $filter('reverse', {});
            }));
    
            it('Should reverse a string', function () { 
                expect(reverse('india')).toBe('aidni'); //pass test
                expect(reverse('don')).toBe('don'); //fail test
            });
    
        });
    
    
    });
    

    In the above code snippet, we are creating a proxy of the module and filter using ng-mock. We have written tests for the filter name ‘reverse’ from module ‘MyApp’ and we are injecting the filter using the $filter service of ng-mock.

    At this point, if we go ahead and run the SpecRunner.html, we will get a failed test result because we have not created the AngularJS filter or the ‘MyApp’ module.

    So now let us go ahead and create an AngularJS Module ‘MyApp’ with filter ‘reverse’. We will create these in the App/CalculatorApp.js

    var MyApp = angular.module("MyApp", []);
    
    MyApp.filter('reverse', [function () {
        return function (string) {
            return string.split('').reverse().join('');
        }
    }]);
    

    Now go back and run once again, run SpecRunner.html. We will find that once again the test has failed. As seen in the failure message, it excpected ‘nod’ to be ‘don’

    If you remember, we deliberately wrote a failed test. So now let’s go ahead and fix the test in CalculatorAppTest.js 

    describe('Calculator App Tests', function () {
    
        beforeEach(module('MyApp'));
    
        describe('reversestringfiltertest', function () {
    
            var reverse;
            beforeEach(inject(function ($filter) { //initialize filter
                reverse = $filter('reverse', {});
            }));
    
            it('Should reverse a string', function () { 
                expect(reverse('india')).toBe('aidni');
                expect(reverse('don')).toBe('nod'); 
            });
    
        });
    
    
    });
    

    When we run the test in SpecRunner.html, we’ll find all tests are passed as shown in the image below.

    Problems in Running Test Using SpecRunner.html

    You may have noticed the only problem in the approach above is that each time we change the test code or source code, we need to go back and either load SpecRunner.html again or refresh it manually to see the updated test result. In real project development, this could be a pain. We need a mechanism so that whenever the code changes, tests should be executed automatically. To do this, we'll use test runner. We have already installed the Karma test runner to automatically run tests whenever code changes, so now let’s configure it to do that.

    Step 14

    To configure Karma, we need to run command Karma init.

  • karma init
  • You will be prompted with many questions to answer, use the following answers as displayed below:

    Karma.cnf.js file has been created after answering these questions. Next, let us open Visual Studio and include the newly created file Karma.conf.js in our project. Open Karma.conf.js and add references to Angular and Angular Mock. Your Files sections should look like the image below:

    files: [
          'node_modules/angular/angular.js',
          'node_modules/angular-mocks/angular-mocks.js',
          'app/*.js',
          'tests/*Test.js'
        ],
    

    Next go back to command prompt and run this command

  •  karma start
  • As soon as we run the karma start command, tests will start executing and we can see test results on the command prompt as shown in the image below.  As you change the code and save the file, the test will be executed automatically.

    Step 15

    Now let’s write a unit test for the AngularJS Controller. Add the following test to CalculatorAppTest.js and add a test for the controller below to the filter test or below the filter test describe.

    describe('addcontrollertest', function () {
            var $controller;
            beforeEach(inject(function (_$controller_) {
                $controller = _$controller_;
            }));
            it('1 + 1 should equal 2', function () {
                var $scope = {};
                var controller = $controller('CalculatorController', { $scope: $scope });
                $scope.num1 = 1;
                $scope.num2 = 2;
                $scope.add();
                expect($scope.sum).toBe(3);
            });
        });
    

    We are creating a proxy of the controller and injecting that using ng-mock $controller service.  Once the controller is injected, take the reference of CalculatorController (controller in the test here) and calling the add method.  At this point the test will fail, since we have not written controller yet.  Karma is giving the failure message that CalculatorController is not a function.

    Next let us go ahead and write the controller in the ‘MyApp’ module.

    MyApp.controller('CalculatorController', function ($scope) {
    
        $scope.add = function () {
            $scope.sum = $scope.num1 + $scope.num2;
        }
    });
    

    As soon as we save the CalculatorApp.js file after writing the controller, the test will pass as shown in the image below:

    Step 16 — Testing AngularJS Factory with local data  

    Let us say that we have created an AngularJS service using factory method name PlayerLocalApi. This service is returning local data which is hard coded as of now. The Service is created as shown in the listing below:

    MyApp.factory('PlayerLocalApi', function () {
        //var data = [{ "Name": "Dhananjay Kumar", "Age": 33.0 }];
        var data = [{ "Id": "1", "Name": "Dhananjay Kumar", "Age": 33.0 }, { "Id": "2", "Name": "Sachin Tendulkar", "Age": 22.0 }, { "Id": "6", "Name": "rahul dravid", "Age": 60.0 }];
        var PlayerLocalApi = {};
        PlayerLocalApi.getPlayers = function () {
            return data;
        }
    
        return PlayerLocalApi;
    });
    

    We can write a unit test to test the service as shown in the listing below:

    describe('playerservicetest', function () {
            var data = [{ "Id": "1", "Name": "Dhananjay Kumar", "Age": 33.0 }, { "Id": "2", "Name": "Sachin Tendulkar", "Age": 22.0 }, { "Id": "6", "Name": "rahul dravid", "Age": 60.0 }];
            var PlayerLocalApi = {};
    
            beforeEach(inject(function (_PlayerLocalApi_) {
                PlayerLocalApi = _PlayerLocalApi_;
            }));
            it('should return search player data', function () {
                expect(PlayerLocalApi.getPlayers()).toEqual(data);
    
            });
    
        });
    

    In the above code snippet, we are injecting the service PlayerLocalApi and then calling getPlayers on that. Also, we have exact test data to test the data returns from the service. This test will pass.

    Just to make sure that we have written the test correctly, go back to service and comment the second var data and uncomment the first var data so the modified service is as listed below:

    MyApp.factory('PlayerLocalApi', function () {
        var data = [{ "Name": "Dhananjay Kumar", "Age": 33.0 }];
       //var data = [{ "Id": "1", "Name": "Dhananjay Kumar", "Age": 33.0 }, { "Id": "2", "Name": "Sachin Tendulkar", "Age": 22.0 }, { "Id": "6", "Name": "rahul dravid", "Age": 60.0 }];
        var PlayerLocalApi = {};
        PlayerLocalApi.getPlayers = function () {
            return data;
        }
    
        return PlayerLocalApi;
    });
    

    Now the service is returning data which is not the same as test data in the test so we will get a failed test as shown in the image below:

    And that’s how to write unit tests for services returning local data.

    Conclusion

    In this post we covered a lot! We learned how to:

    • Set up a development environment
    • Set up a test environment for jasmine
    • Setup a karma test runner environment
    • Write unit tests for filter, controller, and service

    In further posts, we will see how to write unit tests for $http, $q services etc. I hope you find this post useful. Thanks for reading!

    Get Your Apps to Customers 5X Faster with RAD Studio. Brought to you in partnership with Embarcadero.

    Topics:
    angularjs ,javascript ,visual studio ,testing

    Published at DZone with permission of Dhanajay Kumar, DZone MVB. See the original article here.

    Opinions expressed by DZone contributors are their own.

    THE DZONE NEWSLETTER

    Dev Resources & Solutions Straight to Your Inbox

    Thanks for subscribing!

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

    X

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

    {{ parent.tldr }}

    {{ parent.urlSource.name }}