Hot File

Testing: Bootstrap Blocks, Routes, Events, and Animations

View: 301    Dowload: 0   Comment: 0   Post by: hanhga  
Author: none   Category: Javascript   Fields: Other

9 point/2 review File has been tested

In the process of building and delivering full featured software, we apply several techniques to check the correctness and quality of the software. Unit testing is one of these techniques.

Introduction

In the process of building and delivering full featured software, we apply several techniques to check the correctness and quality of the software. Unit testing is one of these techniques. Many organizations pay a lot of attention towards unit testing as it reduces the cost of finding and fixing potential issues of an application.

As we start developing applications with hundreds of thousands of JavaScript lines, we can’t escape from testing the code. Several JavaScript developers say that testing JavaScript is even more important as behavior of the language is unknown until runtime.

Thankfully, AngularJS makes testing the code written using the framework easier by supporting features like Dependency Injection (DI). In three of my past articles, I discussed a few tips on mocking, how to test controllers, services and providers and how to test directives. This article will cover testing Bootstrap blocks of an AngularJS application (includes config blocks, run blocks and route resolve blocks), scope events and animations.

Testing Config and Run blocks

Config and run blocks are executed at the beginning of the life cycle of a module. They contain important logic that controls the way a module, a widget, or an application works. It’s a bit tricky to test them as they can’t be called directly like other components. At the same time, they can’t be ignored as their role is crucial.

Consider the following config and run blocks:

angular.module('configAndRunBlocks', ['ngRoute'])
    .config(function ($routeProvider) {
    $routeProvider.when('/home', {
        templateUrl: 'home.html',
        controller: 'HomeController',
        resolve: {
            bootstrap: ['$q', function ($q) {
                return $q.when({
                    prop: 'value'
                });
            }]
        }
    })
        .when('/details/:id', {
        templateUrl: 'details.html',
        controller: 'DetailsController'
    })
        .otherwise({
        redirectTo: '/home'
    });
})
    .run(function ($rootScope, messenger) {

    messenger.send('Bootstrapping application');
    $rootScope.$on('$locationChangeStart', function (event, next, current) {
        messenger.send('Changing route to ' + next + ' from ' + current);
    });
});

Similarly to the case of testing providers, we need to make sure that the module is loaded before testing the functionality inside the config and run blocks. So, we will use an empty inject block to load the modules.

The following snippet mocks the dependencies used in above block and loads the module:

describe('config and run blocks', function () {
    var routeProvider, messenger;

    beforeEach(function () {
        module('ngRoute');

        module(function ($provide, $routeProvider) {
            routeProvider = $routeProvider;
            spyOn(routeProvider, 'when').andCallThrough();
            spyOn(routeProvider, 'otherwise').andCallThrough();

            messenger = {
                send: jasmine.createSpy('send')
            };
            $provide.value('messenger', messenger);
        });

        module('configAndRunBlocks');
    });

    beforeEach(inject());
});

I intentionally didn’t mock the $routeProvider object as we’ll test the registered routes later in this article.

Now that the module is loaded, the config and run blocks have already been executed. So, we can start testing their behavior. As the config block registers routes, we can check if it registered the right routes. We will test if the expected number of routes is registered. The following tests verify the functionality of the config block:

describe('config block tests', function () {
    it('should have called registered 2 routes', function () {
        //Otherwise internally calls when. So, call count of when has to be 3
        expect(routeProvider.when.callCount).toBe(3);
    });

    it('should have registered a default route', function () {
        expect(routeProvider.otherwise).toHaveBeenCalled();
    });
});

The run block in the sample code calls a service and registers an event. We will test the event later in this article. For the moment, let’s test the call to the service method:

describe('run block tests', function () {
    var rootScope;
    beforeEach(inject(function ($rootScope) {
        rootScope = $rootScope;
    }));
    it('should send application bootstrap message', function () {
        expect(messenger.send).toHaveBeenCalled();
        expect(messenger.send).toHaveBeenCalledWith("Bootstrapping application");
    });
});

Testing Scope Events

Event aggregation is one of the good ways to make two objects interact with each other even when them are totally unaware of each other. AngularJS provides this feature through $emit/$broadcast events on$scope. Any object in the application can raise an event or listen to an event depending on the need.

When an application runs, both subscribers and publishers of the events are available. But, as unit tests are written in isolation, we have only one of the objects available in the unit tests. So, the test spec will have to mimic the other end to be able to test the functionality.

Let’s test the event registered in the run block above:

$rootScope.$on('$locationChangeStart', function (event, next, current) {
    messenger.send('Changing route to ' + next + ' from ' + current);
});

The $locationChangeStart event is broadcasted by the $location service whenever the location of the application changes. As already mentioned, we need to manually fire this event and test if the message is sent by the messenger. The following test performs this task:

it('should handle the $locationChangeStart event', function () {
    var next = '/second';
    var current = '/first';
    rootScope.$broadcast('$locationChangeStart', next, current);
    expect(messenger.send).toHaveBeenCalled();
    expect(messenger.send).toHaveBeenCalledWith('Changing route to ' + next + ' from ' + current);
});

Testing Routes

Routes define the way users navigate the application. Any improper or accidental change in the route configuration will lead to a bad user experience. So, routes should have tests too.

So far, ngRoute and ui-router are the most widely used routers in AngularJS applications. Routes for both of these providers have to be defined in the config block, while route data is made available through services. Route data configured with ngRoute is available through the service $route. Route data of ui-router is available through the service $state. These services can be used to inspect if the right set of routes are configured.

Consider the following config block:

angular.module('configAndRunBlocks', ['ngRoute'])
    .config(function ($routeProvider) {
    $routeProvider.when('/home', {
        templateUrl: 'home.html',
        controller: 'HomeController',
        resolve: {
            bootstrap: ['$q', function ($q) {
                return $q.when({
                    prop: 'value'
                });
            }]
        }
    })
        .when('/details/:id', {
        templateUrl: 'details.html',
        controller: 'DetailsController'
    })
        .otherwise({
        redirectTo: '/home'
    });
});

Let’s now test these routes. As the first thing, let’s get a reference of the $route service:

beforeEach(inject(function ($route) {
    route = $route;
}));

The /home route above has templateUrl, a controller and a resolve block configured. Let’s write assertions to test them:

it('should have home route with right template, controller and a resolve block', function () {
    var homeRoute = route.routes['/home'];
    expect(homeRoute).toBeDefined();
    expect(homeRoute.controller).toEqual('HomeController');
    expect(homeRoute.templateUrl).toEqual('home.html');
    expect(homeRoute.resolve.bootstrap).toBeDefined();
});

Test for the details route would be similar. We also have a default route configured using the otherwise block. The default routes are registered with null as the key value. The following is the test for it:

it('should have a default route', function () {
    var defaultRoute = route.routes['null'];
    expect(defaultRoute).toBeDefined();
});

Testing Resolve Blocks

Resolve blocks are the factories that are created when a route is loaded and they are accessible to the controller associated with the route. It’s an interesting scenario to test as their scope is limited to the route and we still need to get a reference of the object.

The only way I see to test the resolve block is by invoking it using the $injector service. Once invoked, it can be tested like any other factory. The following snippet tests the resolve block configured with the home route that we created above:

it('should return data on calling the resolve block', function () {
    var homeRoute = route.routes['/home'];
    var bootstrapResolveBlock = homeRoute.resolve.bootstrap;
    httpBackend.expectGET('home.html').respond('<div>This is the homepage!</div>');
    var bootstrapSvc = injector.invoke(bootstrapResolveBlock); //[1].call(q);
    bootstrapSvc.then(function (data) {
        expect(data).toEqual({
            prop: 'value'
        });
    });
    rootScope.$digest();
    httpBackend.flush();
});

I had to mimic the templateUrl in the above test as AngularJS tries to move to the default route when the digest cycle is invoked.

The same approach can be used to test $httpInterceptors as well.

Testing Animations

The technique of testing animations has some similarity with testing directives but testing animations is easier since animations are not as complex as directives.

The angular-mocks library contains the module ngAnimateMock to ease the job of testing animations. This module has to be loaded before testing animations.

Consider the following JavaScript animation:

angular.module('animationsApp', ['ngAnimate']).animation('.view-slide-in', function () {
    return {
        enter: function (element, done) {
            element.css({
                opacity: 0.5,
                position: "relative",
                top: "10px",
                left: "20px"
            })
                .animate({
                top: 0,
                left: 0,
                opacity: 1
            }, 500, done);
        },
        leave: function (element, done) {
            element.animate({
                opacity: 0.5,
                top: "10px",
                left: "20px"
            }, 100, done);
        }
    };
});

Let’s now write tests to verify the correctness of this animation. We need to load the required modules and get references of the required objects.

beforeEach(function () {
    module('ngAnimate', 'ngAnimateMock', 'animationsApp');
    inject(function ($animate, $rootScope, $rootElement) {
        $animate.enabled(true);
        animate = $animate;
        rootScope = $rootScope;
        rootElement = $rootElement;
        divElement = angular.element('<div class="view-slide-in">This is my view</div>');
        rootScope.$digest();
    });
});

To test the enter part of the animation defined above, we need to programmatically make an element enter the rootElement referenced in the above snippet.

An important thing to remember before testing animations is that animations are prevented by AngularJS from running till the first digest cycle is completed. This is done to make the initial binding faster. The last statement in the above snippet kicks off the first digest cycle so that we don’t have to do it in every test.

Let’s test the enter animation defined above. It has two test cases:

  1. Element should be positioned at 10px top and 20px left with opacity 0.5 while entering
  2. Element should be positioned at 0px top and 0px left with opacity 1 after 1 sec of entering. This has to be an asynchronous test as the control will have to wait for 1 sec before asserting

The followings are the tests for above two cases:

it('element should start entering from bottom right', function () {
    animate.enter(divElement, rootElement);
    rootScope.$digest();

    expect(divElement.css('opacity')).toEqual('0.5');
    expect(divElement.css('position')).toEqual('relative');
    expect(divElement.css('top')).toEqual('10px');
    expect(divElement.css('left')).toEqual('20px');
});

it('element should be positioned after 1 sec', function (done) {
    animate.enter(divElement, rootElement);
    rootScope.$digest();

    setTimeout(function () {
        expect(divElement.css('opacity')).toEqual('1');
        expect(divElement.css('position')).toEqual('relative');
        expect(divElement.css('top')).toEqual('0px');
        expect(divElement.css('left')).toEqual('0px');
        done();
    }, 1000);
});

Similarly, for the leave animation we need to check the values of the CSS properties after 100ms. Since the test has to wait for the animation to be completed, we need to make the test asynchronous.

it('element should leave by sliding towards bottom right for 100ms', function (done) {
    rootElement.append(divElement);
    animate.leave(divElement, rootElement);
    rootScope.$digest();
    setTimeout(function () {
        expect(divElement.css('opacity')).toEqual('0.5');
        expect(divElement.css('top')).toEqual('10px');
        expect(divElement.css('left')).toEqual('20px');
        done();
    }, 105);
    //5 ms delay in the above snippet is to include some time for the digest cycle
});

 

Testing: Bootstrap Blocks, Routes, Events, and Animations

Testing: Bootstrap Blocks, Routes, Events, and Animations Posted on 08-03-2016  In the process of building and delivering full featured software, we apply several techniques to check the correctness and quality of the software. Unit testing is one of these techniques. 4.5/10 301

Comment:

To comment you must be logged in members.

Files with category

  • JUnit 5 State Of The Union using java

    View: 552    Download: 0   Comment: 0   Author: none  

    JUnit 5 State Of The Union using java

    Category: Javascript
    Fields: Other

    2.25/2 review
    JUnit 5 has been under development for about 14 months now and the prototype is almost a year old. Time to summarize what happened so far, where the project stands, and where it’s going.

  • Getting Started with Dropwizard using java

    View: 618    Download: 0   Comment: 0   Author: none  

    Getting Started with Dropwizard using java

    Category: Javascript
    Fields: Other

    2.25/2 review
    Dropwizard is a framework for building RESTful web services in Java. In this tutorial we’re going to have a look at how to get started with developing a Dropwizard application by building a new service from scratch.

  • Build Query NULL Value in MySql

    View: 288    Download: 0   Comment: 0   Author: none  

    Build Query NULL Value in MySql

    Category: Javascript
    Fields: Other

    2.5/2 review
    Misunderstanding NULL is common mistake beginners do while writing MySql query. While quering in MySql they compare column name with NULL. In MySql NULL is nothing or in simple word it isUnknown Value so if you use comparison operator for NULL values...

  • Manage Your JavaScript Application State with MobX

    View: 302    Download: 0   Comment: 0   Author: none  

    Manage Your JavaScript Application State with MobX

    Category: Javascript
    Fields: Other

    2.25/2 review
    This article was peer reviewed by Michel Weststrate and Aaron Boyer. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

  • Build Bringing Pages to Life with the Web Animations API

    View: 314    Download: 0   Comment: 0   Author: none  

    Build Bringing Pages to Life with the Web Animations API

    Category: Javascript
    Fields: Other

    4.5/2 review
    This article is by guest author Dudley Storey. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the JavaScript community.

  • How to Style Google Custom Search Manually

    View: 291    Download: 0   Comment: 0   Author: none  

    How to Style Google Custom Search Manually

    Category: Javascript
    Fields: Other

    0/0 review
    Website owners very often decide on using Google’s Custom Search Engine (GCSE) for searching through their content instead of using built-in and/or custom search functionality. The reason is simple – it’s much less work, and most often it does the...

  • Test React Components Using Jest

    View: 4903    Download: 0   Comment: 0   Author: none  

    Test React Components Using Jest

    Category: Javascript
    Fields: Other

    4.5/1 review
    This article is by guest author Jack Franklin. SitePoint guest posts aim to bring you engaging content from prominent writers and speakers of the JavaScript community.

  • Programming Question Reverse String without using String function

    View: 752    Download: 0   Comment: 0   Author: none  

    Programming Question Reverse String without using String function

    Category: Javascript
    Fields: Other

    0/0 review
    Write a program to reverse string without using string function. You don’t have to use any in-built string library function. This problem can be solved by multiple approaches. Let’s check it.

 
Newsletter Email

File suggestion for you

File top downloads

logo codetitle
Codetitle.com - library source code to share, download the file to the community
Copyright © 2015. All rights reserved. codetitle.com Develope by Vinagon .Ltd