Testing AngularJS directives with direct model binding and text binding using Jasmine

Hackered
Sunday, September 7, 2014
by Sean McAlinden

For a guide to setting up your test environment using Jasmine & Karma, please take a look at Test driving JavaScript with Jasmine, Grunt, Karma, PhantomJS and Node js. In this post I am going to test drive an AngularJS directive.

Acceptance Criteria

When I bind a user model to the directive Then the users firstname and lastname are printed onto the screen And the user id is set as the value of html container attribute "˜data-user-id' When I add a css class attribute to the directive Then the resulting html container includes the css class

Karma

If you are using Karma, ensure it knows about AngularJS, I show how to do this is in my previous post Mocking http responses in Angular JS controllers with Jasmine.

Test Setup

Create a file called userDirective.test.js. Lets add the setup code:

describe('userDirective tests', function() {

    var $compile;
    var $rootScope;

    beforeEach(module('myDirectiveTestApp'));

    beforeEach(inject(function (_$compile_, _$rootScope_) {
        $compile = _$compile_;
        $rootScope = _$rootScope_;
    }));
});

Essentially this is:

  1. Declaring variables for the $compile and $rootScope which we will need for testing our directive.
  2. Loading our module "˜myDirectiveTestApp' (doesn't exist yet)
  3. Setting the $compile and $rootScope variables

Note: The injector unwraps the underscores (_) from around the parameter names which is important if your variables have the same names as the parameters being injected.

Lets write our test then

After the final before each method, add the following test:

it('should create a basic user overview', function () {
    var user = { id: 1, firstName: "John", lastName: "Smith" };
    var cssClassName = "myClass";
 
    $rootScope.user = user;
 
    var element = $compile("<user-overview userinfo=\"user\" class=\"" 
		+ cssClassName + "\"></user-overview>")($rootScope);
 
    $rootScope.$digest();
 
    expect(element.html())
        .toContain("<div class=\"" + cssClassName + "\" data-user-id=\"" 
		+ user.id + "\">Firstname: " + user.firstName + " Lastname: " 
		+ user.lastName + "</div>");
});

What's going on?

  1. We create an in-memory user object
  2. We add the user object to the root scope
  3. We compile our directive element and pass in the $rootScope (this is important as it will setup model binding between the userinfo attribute and the user we added to the root scope)
  4. We call $rootScope.$digest() which essentially runs all the watchers and compilers
  5. We assert the html that should be produced from our directive

Note: Make sure you set any values on the $rootScope before you run compile 

Lets write the directive

Create a file called userDirective.js.

Step 1: Create the application

var myApp = angular.module('myDirectiveTestApp', []);

Step 2: Create the directive

myApp.directive('userOverview', function () {
    return {
        restrict: 'E',
        scope: {
            user: '=userinfo',
            cssClass: '@class'
        },
        template: '<div class="{{cssClass}}" data-user-id="{{user.id}}">Firstname: {{user.firstName}} Lastname: {{user.lastName}}</div>'
    };
});

What's going on here then?

  1. We have created the directive called userOverview.
  2. We have restricted its use to being an element.
  3. We have added an isolation scope property called user to point to the userinfo attribute for the direct model binding to the user object (notice the "=" sign).
  4. We have added an isolation scope property called cssClass for simple text binding to the class attribute, this is how I will pass the css class to the template (notice the "@" sign).
  5. Finally we have added the template (directly in the directive isn't necessarily the best way to do this, good for post though)

Summary

Hopefully your test went green and you have seen how nice it is to test your directives. Happy testing.