Sunday, December 13, 2015

Instructing Proxyquire To Ignore Nested Requires

When writing unit tests for Node.js applications, you can use the proxyquire npm module to override the modules pulled in by the file under test using require(), replacing them with your own.  So say your file containing the methods you want to test pulls in two other modules using require:


//underTest.js
var moduleA = require( './moduleA' );
var master = require( '../../core/master' );
...
module.exports = {
  runFoo: function() { return moduleA.outputResult() }
};

...in your unit test, you can use a stub library like Sinon in conjunction with proxyquire to instantiate the file under test but with the stubs used in place of the normal modules:


//underTest.spec.js
var sinon = require( 'sinon' );
var proxyquire = require( 'proxyquire' );

describe( 'underTest.js functions', function() {
  var stubModuleA, stubMaster;

  before( function() {

    var stubModuleA = sinon.stub();
    var stubMaster = sinon.stub();

    underTest = proxyquire( './underTest', {
      './moduleA': stubModuleA,
      '../../core/master': stubMaster
    });
  
  });
  
});

...and then you can spy on and define the function behavior of the stubbed objects for your tests.

When I first did this, I ran into problems because one of the modules I was replacing (say moduleA) itself pulled in a module of its own:


//moduleA.js
var main = require( '../../main' );

...and in that main module were functions that executed as soon as the file was loaded via require(), and the execution of those functions caused the before() block of my test file to bomb.

After some research, I discovered that one solution was to use proxyquire to override the require() call in moduleA, and instruct proxyquire to essentially ignore the require by not calling through to it.


//underTest.spec.js (revised)
var sinon = require( 'sinon' );
var proxyquire = require( 'proxyquire' );

describe( 'underTest.js functions', function() {
  var stubModuleA, stubMaster;

  before( function() {

    var stubModuleA = proxyquire( './moduleA', {
      '../../main': { '@noCallThru': true }
    });
    
    var stubMaster = sinon.stub();

    underTest = proxyquire( './underTest', {
      './moduleA': stubModuleA,
      '../../core/master': stubMaster
    });
  
  });
  
});

Wednesday, April 8, 2015

Mild Hack for Unit Testing the Run Method of an Angular Module

In Angular 1.x, the run() method of a module behaves similar to the main method or constructor method concept found in other languages:  it's a method that runs as soon as all of the dependencies have been resovled and the module has been configured.

Because of this, even if you use run() to execute a named method that can be called separately in your unit test, the code in that method gets executed during the process of instantiating the module to use in your tests.  That makes it difficult to do any testing that compares the state of data prior to running the method or tests that require mocking dependencies and dependency behavior inside of the method.

So when I ran into this problem, I came up with a mild "hack" to work around the immediate execution of the run() method.  I made the execution of the code inside my run method dependent on the value of a Boolean constant:


angular.module( 'mainModule', [] )
    .constant( 'executeRunMethod', true )
    .run( runMethod );

function runMethod( $rootScope, authService, executeRunMethod ) {
    if ( executeRunMethod ) {
        //Execute the code as expected
    }
}

runMethod.$inject = [ '$rootScope', 'authService', 'executeRunMethod' ];

With the executeRunMethod constant set to true, the runMethod() code executes via run() once the module is wired up as expected, so everything "runs" normally.

In my unit test however, I can override the executeRunMethod constant during the process of instantiating the module, setting it to false, effectively preventing the run() method from executing.


'use strict';

describe( 'mainModule', function () {
    var $rootScope,
        mockAuthService;

    beforeEach( function () {

        module( 'mainModule', function ( $provide ) {
            // The executeRunMethod constant is overridden to be false
            $provide.constant( 'executeRunMethod', false );
        } );

        inject( function ( $injector ) {
            $rootScope = $injector.get( '$rootScope' );
            mockAuthService = { requestUser: function() {} };
        } );

    });

Later in the test file, I can test the runMethod directly, passing in the needed arguments, including a "true" instance of the executeRunMethod argument allowing the code to be executed:


describe( 'the runMethod function', function() {

        var executeRunMethod = true;

        it( 'performs the expected action', function () {

            runMethod( $rootScope, mockAuthService, executeRunMethod );

            expect( result ).toEqual( expectation );
        });
});

So this arrangement lets me test the code used by the module run() method just as if it was a normal method.

Monday, March 9, 2015

Using a Route Naming Convention to Control View Access in AngularJS

Suppose for a moment that you have an AngularJS single-page application, one with view routes managed with then ngRoute module, that is used by users with different roles.  A user in your company's Sales group has access to certain areas of the application, while a user in Accounting works in other parts of the application.  And there are also some areas of the application that are common to all users.

Now, you already have the navigation menu wired up so that users only see the navigation links appropriate to their user roles.  And even if a Sales user somehow ends up in a view meant for an Accounting user, the server answering the REST calls for the data powering that view is going to check the security token sent with the request and isn't going to honor that request.  But you'd still like to keep users out of UI views that aren't meant for them.

You could do a user access check at the start of each controller, or perhaps within the resolve property of each route, but that would be repetitive and it's something you could forget to do on occasion.

What's cool about the route URLs you can construct with the ngRoute $routeProvider is that they don't have any relation to the actual file structure in your application.  You could define a route like so:


$routeProvider.
  when( '/the/moon/is/full/and/the/night/is/foggy', {
  templateUrl: 'views/fullmoon.html',
  controller: 'moonController'
 })

...and it's perfectly okay as long as the templateUrl and controller are legit.

So let's use that fact about route URLs to solve the challenge presented by having the route URL determine user access to the view attached to the route.

Pretend you have a sessionService module in your Angular application that manages the current session and stores the current user's information in a user object, and that user object has a "roles" property that is an array of all of the security roles that user has. If you add the following code in your main application module (the module named in the ng-app attribute in your application)...


.constant( 'authRoles', [ 'sales', 'accounting' ] )

.run( [ '$rootScope', '$location', 'sessionService', 'authRoles', function( $rootScope, $location, sessionService, authRoles ) {

    $rootScope.$on( '$routeChangeStart', function ( event, next, current ) {
        if( sessionService.isActive() ) {
            var targetRoute = $location.path();
            if( targetRoute.split( '/' )[1] == 'auth' ) {
                var routeRole = targetRoute.split( '/' ).length > 2 ? targetRoute.split( '/' )[2] : '';
                 if( sessionService.getUser() == undefined ) {
                    $location.path( '/login' );
                } else if ( routeRole && authRoles.indexOf( routeRole ) > -1 && sessionService.getUser().roles.indexOf( routeRole ) == -1 ) {
                    $location.path( '/unauthorized' );
                }
            }
        }
    });

 }])

...then when a route change is initiated, the requested route URL will be parsed to determine if the route is secured in any way. Any route URL beginning with "auth" is only accessible to authenticated users, and any URL where the location segment after "auth" matches one of the user roles defined in the "authRoles" constant (in this case, "sales" and "accounting") will only be accessible to a user having that role in their "roles" array.

So the given users attempting to navigate to the given routes will get the given results:

User Route Result
Unauthenticated user /home Routed to "home" view
Unauthenticated user /auth/viewAccount Routed to "/login" (login page)
User with Sales role /auth/viewAccount Routed to "viewAccount" view
User with Sales role /auth/sales/customer/1 Routed to detail page for customer 1
User with Sales role /auth/accounting/stats Routed to "/unauthorized" ("you are not authorized" page)
User with Accounting role /auth/viewAccount Routed to "viewAccount" view
User with Accounting role /auth/accounting/stats Routed to the "stats" view
User with Accounting role /auth/sales/propects Routed to "/unauthorized" ("you are not authorized" page)
User with Accounting role /auth/marketing/addAccount Routed to the "addAccount" view, as "marketing" isn't a recognized role
User with Sales and Accounting roles /auth/accounting/stats Routed to the "stats" view

 

What if you had three roles (Sales, Accounting, HR) and you had a route that you wanted to only be accessible to the Sales and Accounting users?  In that scenario, you'd either have to create a new user role that Sales and Accounting folks would both have, or you'd only secure the route with "auth" and fall back to doing a manual role check in the route's controller or resolve property.  So it's not a technique that solves every scenario, but it's a good first step.

Monday, March 2, 2015

Introducing Sparker: A Codebase Library Management Tool Showcasing AngularJS, Protractor, and Grunt Techniques

Sometimes projects take on a life of their own, and you end up with something unexpected.

I set out to create an template for CRUD-focused single page AngularJS web applications, something I and perhaps my colleagues could use as a foundation for writing new applications.  But under the momentum of self-applied scope creep, what I ended up creating was a Grunt-powered codebase library management tool, with my original template concept as the first codebase of potentially multiple foundational codebases.

After sitting back and looking at what I ended up with, I decided to name the project Sparker for two reasons:

  • I didn't want to use terms like "templates", "scaffolds", or "foundations" for the codebases housed in the project (partly because the project encourages creating demo codebases to accompany the "clean slate" template codebases).  So the codebases are organized into "sparks" and Sparker is the tool for managing them.

  • I wanted to focus on the inspirational aspect of the project:  that, at worst, the techniques in the sparks and in Sparker could "spark" ideas in other developers for how to approach certain problems.

At the end of the day, I see Sparker as a functional proof-of-concept, something individuals or particular teams can play around with or use in their shops to maintain and build off of their own spark libraries, but not something adopted for widespread use by the developer community.  It's not designed to compete with things like Yeoman or Express.js because, like I said, it didn't come out of a design.  But it was fun to develop, and I think there's value in sharing it.

Some of the things you'll find in Sparker today:

  • An example of how to organize Grunt tasks and task options into separate files, and how to execute two different sets of tasks from the same directory/Gruntfile.

  • A Grunt build task that determines which resource files to concatenate based on the <link> and <script> tags in your index.html file.

  • A technique for executing the login process and running certain sets of Protractor tests based on the user role being tested.

  • Convention-based Angular routes for restricting user access based on authentication state/user roles.

  • Example of mocking REST calls within a demo Angular application using the ngMockE2E module.

  • Examples of Angular unit tests and Protractor tests that utilize page objects.

Sparker is available on GitHub at https://github.com/bcswartz/Sparker.

Tuesday, February 24, 2015

Lightning Talk Presentations from the Recent AngularJS DC Meetup

Last week I participated in a series of lightning talks at the AngularJS DC Meetup, hosted by Difference Engine, and I thought I'd share the links to the slide decks and demos presented (unfortunately, the equipment recording the entire event failed, otherwise I would just share that).

Not all of the presenters have posted their material, but here what's been shared so far:

Monday, February 2, 2015

Using Grunt to Concatenate Only the JavaScript/CSS Files Used in Index.html

One of the most common uses of the Grunt task runner is to build a deployment package out of your development code for your website or web application, and part of that build process is usually a task that concatenates the CSS and JavaScript files into singular (or at least fewer) files for optimal download.

The grunt-contrib-concat Grunt plugin allows you to configure a concatenation task to target individual files or entire directories, like so:

concat: {
            js: {
                src: [ 'dev/jquery/jquery.js', 'dev/angular/services/*.js', 'dev/angular/directives/*.js' ],
                dest: '../build/combined.js',
                options: {
                    separator: ';'
                }
            },
        }

The only drawback is that you have to update the task's "src" property as you add or remove CSS and JavaScript assets from your web application.

As I was playing around with Grunt on a personal project, I came to wonder: could I create a Grunt task or set of tasks that could figure out which files to concatenate based on the <link> and <script> tags in my code?  Here's what I came up with.

My project is a single page web application powered by AngularJS.  It has an index.html file that serves as the "single page" that displays the appropriate view fragment (HTML files stored in a "views" directory) for a given page/route in the application.  None of those view fragments require additional CSS or JavaScript resources, so my index.html page pulls down all of the necessary CSS and JavaScript files.

In my index.html file, I wrapped my blocks of <link> and <script> tags with special HTML "build" comments, a set for my CSS block and a set for my JavaScript block:


<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>My HTML Page</title>

    <!--build-css-start-->
    <link rel="stylesheet" media="screen" href="assets/app.css" />
    <link rel="stylesheet" media="screen" href="packages/bootstrap/dist/css/bootstrap.css" />
    <!--build-css-end-->

</head>
<body ng-app="demoApp">

    <!--Div where my Angular views will be displayed-->
    <div ng-view></div>

    <!--build-js-start-->
    <script type="text/javascript" src="packages/angular/angular.js" ></script>
    <script type="text/javascript" src="common/app.js" ></script>
    <script type="text/javascript" src="controllers/loginController.js" ></script>
    <script type="text/javascript" src="controllers/logoutController.js" ></script>
    <script type="text/javascript" src="services/authService.js" ></script>
    <script type="text/javascript" src="services/session.js" ></script>
    <!--build-js-end-->

</body>
</html>

I then created a Grunt task powered by the grunt-replace plugin that finds and parses those blocks via regex, extracting and modifying the file path within each "src" and "href" attribute and appending them to arrays stored as Grunt configuration properities. Each block is replaced by a single <link> and <script> tag pointed at the combined CSS and JavaScript file. The overall build task then executes the concat task, which concatenates all of the files in the respective configuration arrays into the combined files.

So when I run the master build task against my original set of development files:

...I end up with a build directory containing the index.html file, the views folders with the view HTML files, and a single CSS and JavaScript file: 

...and the index.html file itself looks like this:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>My HTML Page</title>

    <link rel="stylesheet" media="screen" href="combined.css"/>

</head>
<body ng-app="demoApp">

    <!--Div where my Angular views will be displayed-->
    <div ng-view></div>

    <script type="text/javascript" src="combined.js"></script>

</body>
</html>

Cool, right?

If you want to try it out for yourself, the entire Gruntfile used to power the build task is below, with comments:

Monday, January 19, 2015

Angular 1.3x Makes ARIA Enhancement Simple With ngAria Module

One of the JavaScript podcasts I listen to mentioned that the one of the new features in Angular 1.3x was the ngAria module.  I knew just from the name that the module had something to do with ARIA (Accessible Rich Internet Applications) but I wasn't sure what it did or how to implement it, so I decided to check it out.

Turns out using this new module is drop-dead simple.  You don't have to add any code to your markup in order to use it:  once you include the ngAria module in your Angular application, it'll automatically add and manage "aria-" attributes on your DOM elements, attributes that help screen readers understand what's going on in your application.

The folks over at Egghead.io have an excellent five minute video that demonstrates ngAria in action:  https://egghead.io/lessons/angularjs-using-ng-aria-to-automatically-improve-your-angularjs-accessibility

You can also read more about ngAria on the AngularJS documentation page on accessibility:  https://docs.angularjs.org/guide/accessibility

Frankly, I can't see a reason for not including this module in your AngularJS application if you're using Angular 1.3x.  It won't solve every accessibility issue, but it's a good starting point.

 

 

Friday, January 16, 2015

Using the "Controller As" Syntax With AngularJS Controllers

I recently had a chance to try out the "controller as" alternative to storing model data and controller functions on the $scope of an Angular controller, and I was rather taken with the elegance of it.

An example of the familar method of attaching a controller to a view via the regular Angular router and exposing data and methods via the $scope:

Route:

...
when('/auth/sales/salesPage', {
  templateUrl: 'views/sales.html',
  controller: 'salesController'
})
...

 

Controller:


.controller( 'salesController', [ '$scope', 'demoService',  function( $scope, demoService ) {
   $scope.uiState = { pageLoaded: false };

   $scope.performAction = function() {
     console.log( "Action!" );
   }
 }]);

 

View:


<h1>Sales Prospects</h1>
<div ng-show="uiState.pageLoaded"></div>

 

...now the same code but using the "controller as" methodology:

 

Route:


...
when('/auth/sales/salesPage', {
  templateUrl: 'views/sales.html',
  controller: 'salesController as vm' //'vm' is now a reference to the controller object
})
...

 

Controller:


.controller( 'salesController', [ 'demoService',  function( demoService ) {
   var vm = this; //The controller object takes the place of the scope.
   vm.uiState = { pageLoaded: false };

   vm.performAction = function() {
     console.log( "Action!" );
   }
 }]);

 

View:


<h1>Sales Prospects</h1>
<-- The route provides the controller object to the view as 'vm' -->
<div ng-show="vm.uiState.pageLoaded"></div>

 

Some of the benefits of this "controller as" methodology:

  • You don't have to inject the $scope into the controller.
  • If you have multiple views with multiple controllers on the same page, you can now have unique references/namespaces for each controller and not worry about name collisions between each controller's $scope.
  • It looks cleaner.
  • Less typing of "$scope" means you won't wear out the 4/$ key on your keyboard as quickly. :)