Showing posts with label Jasmine. Show all posts
Showing posts with label Jasmine. Show all posts

Saturday, 13 June 2015

Everything You Need to Know to Unit Test AngularJS Code

Unit testing is one of the crucial and necessary parts of software development. This phase improves the quality of product, assures accuracy in behavior and also reduces the cost involved in fixing the bugs. There are some good advantages for a developer writing unit tests as well.

AngularJS is one of the most popular framework for building Single Page Applications. One of the key reasons behind its success is, testability. Every piece of code written in AngularJS is unit testable. The features like Dependency Injection make the code written on the framework easier to test.

In the past, I wrote a few posts on unit testing AngularJS controller using Jasmine and QUnit and I also covered a few tips. Over past two years, I worked a lot on AngularJS and thus I wrote a lot of code and tests. This process taught me a lot of tips on unit testing AngularJS code that I wanted to share with the community. As some of you might be aware that I am a regular author for SitePoint, I have put together a series of four articles covering tips on mocking and testing almost every block in AngularJS. Following are the links to these articles:


  1. Mocking Dependencies in AngularJS Tests
  2. Unit Testing in AngularJS: Services, Controllers & Providers
  3. AngularJS Testing Tips: Testing Directives
  4. AngularJS Testing: Bootstrap Blocks, Routes, Events, and Animations


The process of putting these articles together has not been easy for me and at the same time, I enjoyed a lot while writing them. Read them when you get time and feel free to drop any feedback to me.

Happy coding!

Sunday, 28 September 2014

Unit Testing Config and Run Blocks in AngularJS

One of the best-selling points of AngularJS framework is testability. Any piece of code written in an AngularJS application is testable unless it is corrupted by a global object.

All of the blocks in AngularJS except config and run blocks can be instantiated or invoked and tested. Config and run blocks are executed as soon as the module containing the block is loaded into memory. There is no way to call them manually; unless the bodies of these blocks are defined independently and then hooked to a module. But, they are invoked automatically when the module is loaded. So, I don’t see a need to invoke them manually to test their logic.

Say, we have the following module with a config block registering routes and a run block that listens to a global message event to the window:



var app = angular.module('testApp',['ngRoute']);

app.config(function($routeProvider){
  $routeProvider.when('/', {templateUrl:'templates/home.html',controller:'homeCtrl'})
    //definitions of other routes
    .otherwise({redirectTo:'/'});
});

app.run(function($window, $rootScope){
  $window.addEventListener('message', function(event){
    $rootScope.$broadcast(event.data);
  });
});


In test of the config block, we need to see if the methods when and otherwise are called with right parameters. To do that, we must spy these methods and store a reference of $routeProvider as soon as the module in loaded in tests. Providers cannot be mocked using $provide like services as they are not available after config phase. We can pass a callback to module loader and create spies on the methods whose calls have to be inspected.

Following snippet shows how to spy on a provider’s method and a test that checks if the method is called:


describe('testing config block', function() {
  var mockRouteProvider;

  beforeEach(function () {
    module('ngRoute', function ($routeProvider) {
      mockRouteProvider = $routeProvider;
      spyOn(mockRouteProvider, 'when').andCallThrough();
      spyOn(mockRouteProvider, 'otherwise').andCallThrough();
    });
    module('testApp');
  });

  it('should have registered a route for \'/\'', function(){
    expect(mockRouteProvider.when).toHaveBeenCalled();
  });
});


If you run the above test now, it should fail. That’s strange, isn’t it?

I spent a lot of time struggling with it and found two approaches to make the above test pass.

One approach is to have a dummy test before the test that performs an assertion on the logic of config block. You can leave this test empty as it doesn’t have to do anything, or have an assertion that would always pass.


it('doesn\'t have any assertions', function(){});


I didn’t like this approach; as it adds a test that would always pass and doesn’t carry any value. The other approach to make the above test pass is by calling inject() inside a beforeEach block. The inject function is generally used to get references of services that are needed in the tests. Even if there is no need of any service in the tests, the inject block can be called without any callback to bootstrap the modules already loaded using module() blocks.

beforeEach(function(){
  inject();
});


You will see the same issue with run() block as well. It isn’t executed unless a test is executed or inject block is executed.

If you got a better approach to bootstrap modules in tests, feel free to post a comment.

Happy coding!

Tuesday, 31 December 2013

Triggering Events in Angular JS Directive Tests

The best place to perform DOM manipulations in an Angular JS application is in a directive. Sometimes we handle events on the elements wrapped inside the directive to perform required action. The jQlite implementation in Angular JS provides enough APIs to handle these events in the body of the directives. We can use jQuery as well, if API exposed by jQlite is not enough.

One of the primary design goals of Angular JS is testability. Directives are testable too. Since we work directly on DOM with directives, testing becomes a bit tricky. One of such trickier part to test is event. Say, we have a directive that handles blur event on a text box:

app.directive('textBoxBlur', function($window){
  return{
    require:'ngModel',
    link: function(scope, element, attrs, ngModel){
      element.bind('blur', function(e){
        $window.alert("value entered: " + element.val());
      });
    }
  }
});

To call the DOM events manually, jQlite provides a handy method triggerHandler. It is similar to jQuery’s triggerHandler method. It can be fired using jQlite object of any element. Following statement shows this:
elem.triggerHandler('blur');

Since we have to do it repeatedly in tests, it is better to wrap it inside a reusable function as shown below:
changeInputValue = function (elem, value) {
    elem.val(value);
    elem.triggerHandler('blur');
};

Now the above function can be called from any test case to test behaviour of the blur event.
it('Should call alert on losing focus', function(){
  changeInputValue(form.find('input'), "Ravi");
  expect(windowMock.alert).toHaveBeenCalled();
});

Complete sample is available on plnkr: http://plnkr.co/edit/InkyGdIhZiwfe0NC4hyu?p=preview

Happy coding!

Sunday, 22 December 2013

Mocking promises in Angular JS Controller Tests

In a typical Angular JS application, we wrap the calls to backend services in a custom Angular JS service and return a promise from the method in the service to the calling component. For instance, say the calling component is a controller. While testing the controller, we create a mocked service instance with methods replaced by spies. As the original method in the service returns a promise containing the result from executing the backend API, the spy should return a mocked promise with a dummy result.

A few months back, when I was learning Angular and blogging my learning, I did blog posts on unit testing controllers using Jasmine and also on unit testing controllers using QUnit. Back then, I used to use spyOn().andCallThrough() on all methods in the service and used $httpBackend to avoid calling the backend APIs from the service. With time, I understood that this approach is not good as the controller still depends on the logic written inside the service. In this post, we will see how to return mock promises from the spies to isolate controller from the service.

Following are the service and the controller we will be using in this post:

var app = angular.module("myApp", []);
app.factory('dataSvc', function($http, $q){
    var basePath="api/books";
    getAllBooks = function(){
        var deferred = $q.defer();
 $http.get(basePath).success(function(data){
            deferred.resolve(data);
        }).error(function(err){
          deferred.reject("service failed!");
        });
        return deferred.promise;
     };
  
     return{
         getAllBooks:getAllBooks
     };
});

app.controller('HomeController', function($scope, $window, dataSvc){
   function initialize(){
       dataSvc.getAllBooks().then(function(data){
           $scope.books = data;
       }, function(msg){
          $window.alert(msg);
       });
   }

  initialize();
});

Let’s create a spy for the service. This is a bit tricky, as we need to force the promise to pass or fail based on a condition. At the same time, it is simple as $q provides the ready methods when and reject to make our job easier. Following is the spy for getAllBooks method:
var succeedPromise;
spyOn(booksDataSvc, "getAllBooks")
    .andCallFake(function(){
        if (succeedPromise) {
            return $q.when(booksData);
        }
        else{
            return $q.reject("Something went wrong");
        }
    });

The fake implementation of getAllBooks passes if value of the field succeedPromise is set to true, otherwise it fails. We need to manipulate this field in the test cases.

In the test cases, we need to call scope.$digest before checking the expectations, as the promise is triggered from a spy method, which is in non-angular world.

Following test case checks if the books object is set to some value when the promise passes.

it('Should call getAllBooks on creating controller', function(){
    succeedPromise = true;
    createController();
    homeCtrlScope.$digest();
    expect(booksDataSvc.getAllBooks).toHaveBeenCalled();
    expect(homeCtrlScope.books.length).not.toBe(0);
  });

The promise can be forced to fail by just setting succeedPromise to false in the test case. Following test case demonstrates it:
it('Should alert a message when service fails', function(){
    succeedPromise = false;
    createController();
    homeCtrlScope.$digest();
    expect(booksDataSvc.getAllBooks).toHaveBeenCalled();
    expect(windowMock.msg).not.toBe("");
  });

The complete sample is available on plnkr: http://plnkr.co/edit/xD9IPb6TRduAUwRGbIIG

Happy coding!

Saturday, 6 April 2013

Unit Testing AngularJS Controller Using Jasmine

Building and using single page JavaScript applications is fun. It brings richness on client side without having to install any external plugins. While building these applications, we write a lot of JavaScript code to run on client side. Releasing such large amount of code to client without testing will be a sin. We might leave the code with some unnoticed bugs if we do not test.

Right from the first post on Angular JS, I have mentioned several times that unit testability is one of its primary design goals. Tutorial series on Angular’s website demonstrates end-to-end testing using Jasmine’s BDD style. I cannot have sound sleep unless I write some tests for the AngularShoppingCart application. In this post, we will see how a controller can be unit tested using Jasmine.

Note: If you are looking for a tutorial on unit testing using QUnit, I have a blog post on it: Unit Testing Angular JS Controller Using QUnit and Sinon

If you haven’t followed earlier posts, take a look at the code on GitHub.

Jasmine and setting up
Jasmine is a BDD (Behaviour Driven Development) framework for testing JavsScript code. It doesn’t depend on any other JavaScript library. It has nice support to create spies (mocks), which helps to easily get rid of some concrete dependencies. Jasmine can be used to unit test anything written in JavaScript.

In order to run jasmine tests and view their results, we need to create an HTML page. Following references should be added to the page:

  1. Jasmine core library
  2. Jasmine HTML library
  3. Jasmine style sheet
  4. Source files of the script to be tested, which is also referred as System under test (SUT)
  5. File containing unit tests for the above source
  6. Any other library (like jQuery, jQuery UI, Angular) on which the source depends

As we will be testing code written using Angular JS, we should include angular-mocks.js library, which has mock services to replace some of the most commonly used services.

In body tag of the HTML page, a couple of statements are to be added to bootstrap Jasmine. Following is the template of a Jasmine spec runner page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Jasmine Test Runner</title>

    <!-- Jasmine stylesheet -->
    <link rel="stylesheet" type="text/css" href="../../Styles/Jasmine.css">
 
    <!-- Jasmine core and HTML -->
    <script type="text/javascript" src="../../Scripts/jasmine.js"></script>
    <script type="text/javascript" src="../../Scripts/jasmine-html.js"></script>
    <script type="text/javascript" src="../../Scripts/jasmine.async.js"></script>
 
    <!-- JavaScript libraries on which source depends -->
    <script type="text/javascript" src="../../Scripts/angular.js"></script>
    <script type="text/javascript" src="../../Scripts/angular-mocks.js"></script>
 
    <!-- Script source to test and other files on which source depends -->
    <script type="text/javascript" src="../../Scripts/app/ShoppingModule.03.js"></script>
    <script type="text/javascript" src="../../Scripts/app/ShoppingCartController.03.js"></script>
    <script type="text/javascript" src="../../Scripts/app/CartCheckoutController.js"></script>
    
    <!-- Test script -->
    <script type="text/javascript" src="ShoppingCartControllerSpec.js"></script>
    <!--<script type="text/javascript" src="CartCheckoutTestSpec.js"></script>-->
</head>
<body>
    <script type="text/javascript">
        jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
        jasmine.getEnv().execute();
    </script>
</body>
</html>

Specs and Suits
Jasmine has a set of functions defined to create suits, specs and assertions. Following listing explains each of them in brief:

  1. describe: Used to define a suite. Accepts two parameters, name of the suite and a function containing statements to be contained in a suite
  2. it: Used to define a spec. Accepts a name and function containing logic to be executed. The logic contains assertions to evaluate the behaviour
  3. beforeEach: To set up dependencies before any spec under a suite runs
  4. afterEach: To clear any dependency after being used in the test cases
  5. expect: A function that accepts an object to be asserted
  6. matcher: Jasmine defines several matchers like toBe, toHaveBeenCalled, toEqual to perform a Boolean match between actual and expected values. We can write custom matchers too

In-depth discussion about above topics is beyond the scope of this article. Refer to Jasmine’s documentation for more details.

Unit testing ShoppingCartController
Let’s start testing the functions defined in ShoppingCartController.

Dependencies of the controller are clearly visible from the signature. As we need to inspect behaviour of the controller in isolation, we must mock these services. Following is the signature of ShoppingCartController:

function ShoppingCartCtrl($scope, $window, shoppingData, shared) {
}

As these services will be used across specs, it is better to create them globally and initialize them in beforeEach block. Since we will not hit the actual service, we need to use some static data to make the job of testing AJAX calls easier.
var shoppingCartStaticData = [
    { "ID": 1, "Name": "Item1", "Price": 100, "Quantity": 5 },
    { "ID": 2, "Name": "Item2", "Price": 55, "Quantity": 10 },
    { "ID": 3, "Name": "Item3", "Price": 60, "Quantity": 20 },
    {"ID": 4, "Name": "Item4", "Price": 65, "Quantity": 8 }
];
 
describe("ShoppingCartCtrl", function () {
 
    //Mocks
    var windowMock, httpBackend, _shoppingData, sharedMock;
 
    //Controller
    var ctrl;
 
    //Scope
    var ctrlScope;
 
    //Data
    var storedItems;
 
    //Loading shopping module
    beforeEach(function () {
        module("shopping");
    });
 
    beforeEach(inject(function ($rootScope, $httpBackend, $controller, shoppingData) {
        //Mock the services here
    }

});

The second beforeEach block will set-up all dependencies for the controller. So, it needs a number of services to perform its job. We will discuss their importance in shortly.

Resolving Dependencies
First and most important dependency of the controller is the $scope service. We need to create our own scope and pass it as a parameter while creating object of the controller. Using $rootScope, it is very easy to create our own scope:

ctrlScope = $rootScope.$new();

Second dependency is the $window service. As we are using href property of location alone, we can create a custom object with this property alone.
windowMock = { location: { href: ""} };

Third dependency shoppingData service is a wrapper to call backend data services. It used another service, $http to send AJAX requests. Angular JS team has made our job easy by creating $httpBackend, a mock for $http. $httpBackend provides a nice interface to send our own response when an AJAX request is made.

shoppingData service has three functions: getAllItems, addAnItem and removeItem. Let’s create spies for these functions as follows:

httpBackend = $httpBackend;
_shoppingData = shoppingData;
spyOn(shoppingData, 'getAllItems').andCallThrough();
spyOn(shoppingData, 'addAnItem').andCallThrough();
spyOn(shoppingData, 'removeItem').andCallThrough();

The function andCallThrough delegate calls to the actual implementations. The function getAllItems sends an HTTP GET request to the service. Following statement configures a custom response on $httpBackend when Angular detects any such request:
httpBackend.expectGET('/api/shoppingCart/').respond(storedItems);

Fourth and final dependency is the shared service. Following snippet creates a mock shared service with a spy for setCartItems, the only function used in ShoppingCartCtrl:
sharedMock = {
    setCartItems: jasmine.createSpy('setCartItems')
};

Now that we have all the mocks ready, let’s create an object of the controller.
ctrl = $controller(ShoppingCartCtrl, { $scope: ctrlScope, $window: windowMock, shoppingData: _shoppingData, shared: sharedMock });

Testing behaviour of the controller
On creation of the controller, it calls getAllItems function of shoppingData service to fetch details of all items. The test for this behaviour should check if the right function is called and if it sets value to the items property. Following test shows this:

it("Should call getAllItems function on creation of controller and set items property", function () {
    expect(_shoppingData.getAllItems).toHaveBeenCalled();
    httpBackend.flush();
    expect(ctrlScope.items.length).not.toBe(0);
});

Upon calling addItem function of the controller, it calls addAnItem function of shoppingData service. As it makes an HTTP post request to the service, $httpBackend should be configured to respond when it finds a post request. Test looks as follows:
it("Should call addAnItem function of the shoppingData service", function () {
    httpBackend.expectPOST('/api/shoppingCart/', {}).respond(storedItems.push({ "Id": 5, "Name": "Item5", "Price": 70, "Quantity": 10 }));
    ctrlScope.addItem({});
    expect(_shoppingData.addAnItem).toHaveBeenCalled();
    httpBackend.flush();
    expect(storedItems.length).toBe(5);
});

The function removeItem can also be tested in similar way. But, what if a request fails? The $errorMessage property of scope should be assigned with a friendly error message. A request can be forced to fail by passing a JavaScript object literal with an error status code to $httpBackend. Let’s see this in action:
it("Should assign an error message", function () {
    httpBackend.expectDELETE('/api/shoppingCart/1').respond({ status: 500 });
    ctrlScope.removeItem(1);
    expect(ctrlScope.errorMessage).not.toBe("");
});

mySortFunction is used to convert numeric value to number. We can test this function by passing a number in the form of a string and checking if it returned a number to us. We need to set the property sortExpression before calling the function.
it("Should return a number when a number is passed in", function () {
    var item = { "Number": "123" };
    ctrlScope.sortExpression = "Number";
    var numVal = ctrlScope.mySortFunction(item);
    expect(typeof numVal).toBe("number");
});

The totalPrice function is very easy to test, as we need to just check if it sets some value to the returned variable.

On click of Purchase Items link on the page, the user has to be navigated to CheckoutItems view and setCartItems function of shared service should be called to pass the items array to the second view. As we are setting navigation URL to window.location.href, for which we created a mock, the test has to check if this property is set to some value. Following test verifies these functionalities:

it("Should set value in shared and value of href set", function () {
    ctrlScope.items = storedItems;
    ctrlScope.purchase();
 
    expect(sharedMock.setCartItems).toHaveBeenCalled();
    expect(windowMock.location.href).not.toBe("");
});

Now view the test runner page on browser. All the tests should be passed. I encourage you to play a bit with the code and tests and check the result of test after the changes. This way, we will get to know more about Jasmine's behavior as well as unit testing.

You can download the code including unit tests from the following GitHub repo: AngularShoppingCart

Happy coding!