Monday, June 30, 2014

Unit testing an Angular (Ionic) controller using Facebook's Jest

In a previous blog I wrote about using Facebook's jest to unit test an Angular service.  Please refer to this blog to see the full list of dependencies and test setup files.

This blog will build on that one and show how to unit test a controller.


First step

In my testLoader.js header file I add my areaController the same way I added areaService.


Controller
The controller has a dependency on the areaService that we unit tested in the previous article.  It also handles a click event which will set a $rootScope variable.  The actual page transition happens in the view via Ionic's state routing.  See below for the actual view code.
angular.module('locationApp').controller('AreaCtrl',
  function($rootScope, $scope, AreaService) {
    $scope.areas = AreaService.all();

    $scope.rowClick = function(areaName) {
      $rootScope.areaName = areaName;
    };
  }
);


Unit Test

I want my unit test to verify that the $scope object has the right areas and that when the click event happens, the $rootScope variable is set correctly.

We are going to mock the areaService object with fake data and we'll also tell the controller injection service to use our mock.

By calling the inject function in beforeEach, I'm able to make class-level variables to check the $scope and $rootScope of the controller.  Note that most tutorials online choose to name these class-level variables with a leading $, but I chose not to do them that way.
require('../../testLoader');

describe('area controller', function() {
  var areaService,
      controller,
      rootScope,
      scope;

  beforeEach(function () {
    areaService = {
      all: function() { return [{id:1,name:'Area1'},{id:2,name:'Area2'}]; }
    };

    angular.mock.module('locationApp', function($provide) {
      $provide.value('AreaService', areaService);
    });

    inject(function($rootScope, $controller) {
      scope = $rootScope.$new();
      rootScope = $rootScope;
      controller = $controller('AreaCtrl', {
        $scope: scope
      });
    });
  });

  it('returns the correct number of areas in $scope.areas', function() {
    var areas = scope.areas;
    expect(areas.length).toBe(2);
    expect(areas[0].name).toBe('Area1');
  });

  it('sets the $rootScope.areaName when a rowClick event is raised', function() {
    expect(rootScope.areaName).toBeFalsy();
    scope.rowClick('Test Area');
    expect(rootScope.areaName).toBeTruthy();
  });
});


View

FYI, I'm using the BindOnce library, hence the bo-text directive.

<ion-view title="Areas">
  <ion-content class="has-header">
    <ion-list>
    <ion-item bindonce="" href="#/tab/facilities/{{area.id}}" ng-click="rowClick(area.name)" ng-repeat="area in areas | orderBy:'name'" type="item-text-wrap">
        <span bo-text="area.name"></span>
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

Thursday, June 12, 2014

Top 5 things to tell your kid about software development

Here's an article I wrote for Geneca earlier this month.

When I was a kid I was told not to do a lot of things. Did that always stop me? Not really. That’s part of being a kid. It’s in our nature to defy logic, be annoying, and satisfy our curiosity.
That said (and because I know my kids will inevitably be as immersed in technology as I am), these are the top five things I plan to tell my kids about software development.
  1. Keep playing with those legos. Sure, you’ve built a sweet looking airplane . But I bet if you start over again and use some of these new pieces we just bought, you could come up with something even better. (The idea here is that that if you start over, you can do better than you did last time and find ways to overcome the problems that once tripped you up.)
  2. Go ahead and ride those scary rides at the amusement park. Sometimes you need to take risks and try something new. Stretch yourself beyond what you’re used to and what you know. Exercise your creative thinking as often as possible and see if what you come up with is better than what has been done before. When you’re a developer eager to learn new languages, you’ll likely discover useful idioms that you can port into your primary language.
  3. You SHOULD chew with your mouth open. Let others see what you’re working on and explain to them why you chose to do it. It’s the only way to learn how to improve something. Sharing your code can make you feel self-conscious because you’re exposing yourself and your thoughts for others to be critical of. The upside is that any feedback or advice that helps you be better is reward worth the risk.
  4. Being picked last is a good thing. That’s because it usually means you get to learn something new. People have a tendency to play to their strengths. So, when it comes down to working on tasks, they’ll snatch up stuff they know how to do. What may be left for you are the extremely difficult tasks or things that are the most unknown and difficult to solve. And what’s more rewarding than figuring out super tough problems?
  5. It’s OK to ask “Why” a million times. Libraries and frameworks evolve quickly these days and there are tons to choose from. Figuring out why something works, why code was structured a certain way, or why a certain design pattern was chosen, helps grow your knowledge base. Don’t let black box software remain a mystery to you. Dissect what you can.
So kids (and adults): Software development is a lot of trial and error. A lot! Making mistakes is important for growth. Experiencing pain makes you appreciate better and more efficient techniques. This kind of growth helps you level up faster and have much more experience to draw upon.

Tuesday, June 3, 2014

Unit testing your Ionic + Angular app with Facebook's Jest

Getting Started

I've been a big fan of Jasmine for a while.  I've blogged about it, presented it as a lunch and learn, and I teach it as part of our developer training here at Geneca.  We use the Chutzpah Visual Studio extension to automatically run all Jasmine tests on builds and it's pretty sweet.

When I learned about Jest I was really interested because when I'm writing apps in my spare time, I'm not using Visual Studio.  More often than not I'm using Node.  The more popular alternative to Jest is Karma.  I know very little about Karma but I've seen it when Yeoman was used to generate an Angular app.  It seems like it starts up an actual browser instance when it executes test, so it's great for end to end testing.  Jest looks like it doesn't rely on a browser to execute your tests.


My stack

Ionic is a new (at current time) mobile framework that integrates well with Angular, and that sounds exciting to me.  I've built a custom enterprise app using Kendo Mobile (now Telerik AppBuilder) and instead of using the built-in MVVM objects for data-binding I used Angular as I was more familiar and was having a difficult time with MVVM.

All in all I'm using Ionic, Angular, AngularUI Router, Angular-Animate, Angular-Sanitize, Angular-Mocks (just for testing), BindOnce, Lodash, and Gulp.


Getting all the right references

Ionic.bundle.js is a combination of several js files (as specified in the comments block at the top), and the version of Angular that is included by default was not compatible with Angular-Mocks.  The version of Angular-Mocks that I used is linked at the end of this blog.

To mitigate this I use Jest to require all of the combined files separately, pulling in the correct version of Angular that I need.  I actually have a gulp task to build me a new Ionic file that I rename to ionic.dynamic.bundle.js for the actual app.

I created a js file in the project root folder named testIncludes.js.  Here are the contents:

var files = {
  ionic: './www/lib/ionic/js/ionic',
  angular: './www/lib/ionic/js/angular/angular',
  angularAnimate: './www/lib/ionic/js/angular/angular-animate',
  angularSanitize: './www/lib/ionic/js/angular/angular-sanitize',
  angularUIRouter: './www/lib/ionic/js/angular-ui/angular-ui-router',
  ionicAngular: './www/lib/ionic/js/ionic-angular',
  angularMocks: './www/lib/ionic/js/angular/angular-mocks',
  bindOnce: './www/lib/bindonce',
  lodash: './www/lib/lodash.min',
  app: './www/js/app',
  areaService: './www/js/services/areaService',
  store: './www/js/services/store'
};

window.Event = {};

var vals = {};
localStorage = {
  getItem: function(key) { return vals[key]; },
  setItem: function(key, value) { vals[key] = value + ''; },
  clear: function() { vals = {}; }
};

jest
  .dontMock(files.ionic)
  .dontMock(files.angular)
  .dontMock(files.angularAnimate)
  .dontMock(files.angularSanitize)
  .dontMock(files.angularUIRouter)
  .dontMock(files.ionicAngular)
  .dontMock(files.angularMocks)
  .dontMock(files.lodash)
  .dontMock(files.bindOnce)
  .dontMock(files.app)
  .dontMock(files.areaService);

_ = require(files.lodash);
require(files.ionic);
require(files.angular);
require(files.angularAnimate);
require(files.angularSanitize);
require(files.angularUIRouter);
require(files.ionicAngular);
require(files.angularMocks);
require(files.bindOnce);
require(files.app);
require(files.store);
require(files.areaService);

Some gotchas

There is a line that reads window.Event = {};
This line is needed as the Jest tests don't have access to some of the built-in window objects.
I also needed to wire up localStorage for the same reason.

I wish there was a way to combine telling Jest to dontMock a library in the same line that I require it.  Seems kind of redundant.


Testing an angular service that relies on all the above plus another angular service

My angular service looks like this:

angular.module('locationApp').factory('AreaService',
  function(Store) {
    var areas = Store.areas;

    function get(areaId) {
      for (var idx in areas) {
        if (areas[idx].id == areaId) return areas[idx];
      }
    }

    return {
      all: function() { return areas; },
      get: get
    };
  }
);

The test file located in <root>/__tests__/services/areaService-test.js looks like this:

require("../testIncludes");

describe('area service', function() {
  var storeMock,
      service;

  beforeEach(function () {
    storeMock = { areas: [{id:1,name:'Area1'},{id:2,name:'Area2'}] };

    angular.mock.module('locationApp', function($provide) {
      $provide.value('Store', storeMock);
    });

    inject(function(_AreaService_) {
      service = _AreaService_;
    });
  });

  it('returns the correct number of areas from all() function', function() {
    var areas = service.all();
    expect(areas.length).toBe(2);
  });
});

Before each test is run I mock out the Store object with some test data. The real Store service makes an actual ajax call for data, something I don't want to happen in my testing.

Most examples I've seen online for using Angular-Mocks say to just use the module method in your beforeEach but I found that I had to use angular.mock.module.  Within that call you have the opportunity to override Angular's dependency injection provider to use your mock objects.

The inject method is where you extract out the object(s) you want to run your tests against.  I want to run tests against the AreaService object so I grab it from the inject method and assign it to a class-level variable that I can call from all my tests.  Notice the leading and trailing underscores?  Those are completely optional.  The point is that if you use them, the mock library will strip them out when it resolves it to the right class, but it gives you the opportunity to give a local variable the same name if you so choose.

References:
At the time of this writing, I've been using Angular-Mock 1.2.16 obtained from this project: https://github.com/angular/bower-angular-mocks/blob/master/angular-mocks.js
I also got some tips from this article as well:  http://www.benlesh.com/2013/06/angular-js-unit-testing-services.html

Here is part 2 on Unit Testing an Angular Controller