Unit Testing AngularJS Controllers, Views, and More: Part Five

I’ll speak of testing directives using Duck-Angular in this post. This is a follow-up to the series of posts on using Duck-Angular for unit testing controller, views, and bindings (Part 1, Part 2, Part 3, and Part 4), Duck-Angular and the AngularJS-RequireJS-Seed now support AngularJS 1.2.9.

Our Example Directive

Let’s use a simple directive to illustrate our unit test. directive1.js has some code which formats US phone numbers, as below:

define([], function() {
    return function () {
        return {
            link: function (scope, element, attrs) {
                    attrs.$observe("phone", function(value) {
                        var rawPhoneNumber = value.replace(/[^0-9]/g, "");
                        rawPhoneNumber = rawPhoneNumber.slice(rawPhoneNumber.length - 10, 10);

                        var areaCode = rawPhoneNumber.substr(0,3);
                        var phonePrefix = rawPhoneNumber.substr(3,3);
                        var phoneSuffix = rawPhoneNumber.substr(6,4);

                        if (rawPhoneNumber.length === 10) {
                            var prettyPrintedNumber = "(" + areaCode + ")" + " " + phonePrefix + "-" + phoneSuffix;
                            element.text(prettyPrintedNumber);
                        }
                        else {
                            element.text(rawPhoneNumber);
                        }
                });
            }
        };
    };
});

The code is pretty simple, really. It observes the value of the phone attribute on an element (which is resolved from the injected scope variable phoneNumber), and modifies the resultant text of the associated element to the formatted phone number.

The Unit Test

The test, taken from directive1-test.js, is below:

define(["spec_helper"], function(mother) {
    describe("Phone Directive", function() {
        it("can format phone number", function() {
            var template = "<span phone='{{phoneNumber}}'>{{phoneNumber}}</span>";
            var scope = {phoneNumber: "1234567890"};
            return mother.compileDirective(template, scope).spread(function(s, element) {
                expect(element.text()).to.eql("(123) 456-7890");
            });
        });
    });
});

If you’ve followed along in the previous posts, this test will be very familiar to you. The only difference is that, instead of calling mother.createController(…) or mother.createMvc(…), we call mother.compileDirective(…). This is useful for when you are just interested in testing the directive in isolation. If you’re testing a complete partial view, mother.createMvc(…) should serve just as well.

One Caveat

There is one thing to keep in mind: if you modify the scope after the initial compilation, do remember to call s.$apply() before making any assertions. Here’s an example:

define(["spec_helper"], function(mother) {
    describe("Phone Directive", function() {
        it("can format phone number", function() {
            var template = "<span phone='{{phoneNumber}}'>{{phoneNumber}}</span>";
            var scope = {phoneNumber: "1234567890"};
            return mother.compileDirective(template, scope).spread(function(s, element) {
                expect(element.text()).to.eql("(123) 456-7890");
                s.phoneNumber = "5671234400";
                s.$apply();
                expect(element.text()).to.eql("(567) 123-4400");
            });
        });
    });
});

As to what compileDirective() does, most of it is pretty similar to binding views to scopes. The function is more or less similar to createMvc() with some of the fat stripped out.