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>

1 comment: