Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

An introduction to testing AngularJS directives

Save for later
  • 14 min read
  • 01 Apr 2015

article-image

In this article by Simon Bailey, the author of AngularJS Testing Cookbook, we will cover the following recipes:

  • Starting with testing directives
  • Setting up templateUrl
  • Searching elements using selectors
  • Accessing basic HTML content
  • Accessing repeater content

(For more resources related to this topic, see here.)

Directives are the cornerstone of AngularJS and can range in complexity providing the foundation to many aspects of an application. Therefore, directives require comprehensive tests to ensure they are interacting with the DOM as intended. This article will guide you through some of the rudimentary steps required to embark on your journey to test directives. The focal point of many of the recipes revolves around targeting specific HTML elements and how they respond to interaction. You will learn how to test changes on scope based on a range of influences and finally begin addressing testing directives using Protractor.

Starting with testing directives

Testing a directive involves three key steps that we will address in this recipe to serve as a foundation for the duration of this article:

  1. Create an element.
  2. Compile the element and link to a scope object.
  3. Simulate the scope life cycle.

Getting ready

For this recipe, you simply need a directive that applies a scope value to the element in the DOM. For example:

angular.module('chapter5', [])
.directive('writers', function() {
   return {
     restrict: 'E',
     link: function(scope, element) {
       element.text('Graffiti artist: ' + scope.artist);
     }
   };
});

How to do it…

  1. First, create three variables accessible across all tests:
    •     One for the element: var element;
    •     One for scope: var scope;
    •     One for some dummy data to assign to a scope value: var artist = 'Amara Por Dios';

  2. Next, ensure that you load your module:
    beforeEach(module('chapter5'));

  3. Create a beforeEach function to inject the necessary dependencies and create a new scope instance and assign the artist to a scope:
    beforeEach(inject(function ($rootScope, $compile) {
    scope = $rootScope.$new();
    scope.artist = artist;
    }));

  4. Next, within the beforeEach function, add the following code to create an Angular element providing the directive HTML string:
    element = angular.element('<writers></writers>');

  5. Compile the element providing our scope object:
    $compile(element)(scope);

  6. Now, call $digest on scope to simulate the scope life cycle:
    scope.$digest();

  7. Finally, to confirm whether these steps work as expected, write a simple test that uses the text() method available on the Angular element. The text() method will return the text contents of the element, which we then match against our artist value:
    it('should display correct text in the DOM', function() {
    expect(element.text()).toBe('Graffiti artist: ' + 
    artist); });

    Here is what your code should look like to run the final test:

    var scope;
    var element;
    var artist;
     
    beforeEach(module('chapter5'));
     
    beforeEach(function() {
    artist = 'Amara Por Dios';
    });
     
    beforeEach(inject(function($compile) {
    element = angular.element('<writers></writers>');
    scope.artist = artist;
    $compile(element)(scope);
    scope.$digest();
    }));
     
    it('should display correct text in the DOM', function() {
       expect(element.text()).toBe('Graffiti artist: ' + 
    artist); });

How it works…

In step 4, the directive HTML tag is provided as a string to the angular.element function. The angular element function wraps a raw DOM element or an HTML string as a jQuery element if jQuery is available; otherwise, it defaults to using Angular's jQuery lite which is a subset of jQuery. This wrapper exposes a range of useful jQuery methods to interact with the element and its content (for a full list of methods available, visit https://docs.angularjs.org/api/ng/function/angular.element).

In step 6, the element is compiled into a template using the $compile service. The $compile service can compile HTML strings into a template and produces a template function. This function can then be used to link the scope and the template together. Step 6 demonstrates just this, linking the scope object created in step 3. The final step to getting our directive in a testable state is in step 7 where we call $digest to simulate the scope life cycle. This is usually part of the AngularJS life cycle within the browser and therefore needs to be explicitly called in a test-based environment such as this, as opposed to end-to-end tests using Protractor.

There's more…

One beforeEach() method containing the logic covered in this recipe can be used as a reference to work from for the rest of this article:

beforeEach(inject(function($rootScope, $compile) {
// Create scope
scope = $rootScope.$new();
// Replace with the appropriate HTML string
element = angular.element('<deejay></deejay>');
// Replace with test scope data
scope.deejay = deejay;
// Compile
$compile(element)(scope);
// Digest
scope.$digest();
}));

See also

  • The Setting up templateUrl recipe
  • The Searching elements using selectors recipe
  • The Accessing basic HTML content recipe
  • The Accessing repeater content recipe

Setting up templateUrl

It's fairly common to separate the template content into an HTML file that can then be requested on demand when the directive is invoked using the templateUrl property. However, when testing directives that make use of the templateUrl property, we need to load and preprocess the HTML files to AngularJS templates. Luckily, the AngularJS team preempted our dilemma and provided a solution using Karma and the karma-ng-html2js-preprocessor plugin. This recipe will show you how to use Karma to enable us to test a directive that uses the templateUrl property.

Getting ready

For this recipe, you will need to ensure the following:

  1. You have installed Karma
  2. You installed the karma-ng-html2js-preprocessor plugin by following the instructions at https://github.com/karma-runner/karma-ng-html2js-preprocessor/blob/master/README.md#installation.
  3. You configured the karma-ng-html2js-preprocessor plugin by following the instructions at https://github.com/karma-runner/karma-ng-html2js-preprocessor/blob/master/README.md#configuration.
  4. Finally, you'll need a directive that loads an HTML file using templateUrl and for this example, we apply a scope value to the element in the DOM. Consider the following example:
    angular.module('chapter5', [])
    .directive('emcees', function() {
       return {
         restrict: 'E',
         templateUrl: 'template.html',
         link: function(scope, element) {
           scope.emcee = scope.emcees[0];
         }
       };
    })

An example template could be as simple as what we will use for this example (template.html):

<h1>{{emcee}}</h1>

How to do it…

  1. First, create three variables accessible across all tests:
    •     One for the element: var element;
    •     One for the scope: var scope;
    •     One for some dummy data to assign to a scope value: var emcees = ['Roxanne Shante', 'Mc Lyte'];

  2. Next, ensure that you load your module:
    beforeEach(module('chapter5'));

  3. We also need to load the actual template. We can do this by simply appending the filename to the beforeEach function we just created in step 2:
    beforeEach(module('chapter5', 'template.html'));

  4. Next, create a beforeEach function to inject the necessary dependencies and create a new scope instance and assign the artist to a scope:
    beforeEach(inject(function ($rootScope, $compile) {
    scope = $rootScope.$new();
    Scope.emcees = emcees;
    }));

  5. Within the beforeEach function, add the following code to create an Angular element providing the directive HTML string:
       element = angular.element('<emcees></emcees>');

  6. Compile the element providing our scope object:
    $compile(element)(scope);

  7. Call $digest on scope to simulate the scope life cycle:
    scope.$digest();

  8. Next, create a basic test to establish that the text contained within the h1 tag is what we expect:
    it('should set the scope property id to the correct initial 
    value', function () {});

  9. Now, retrieve a reference to the h1 tag using the find() method on the element providing the tag name as the selector:
    var h1 = element.find('h1');

  10. Finally, add the expectation that the h1 tag text matches our first emcee from the array we provided in step 4:
    expect(h1.text()).toBe(emcees[0]);

  11. You will see the following passing test within your console window:

    introduction-testing-angularjs-directives-img-0

How it works…

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at AU $19.99/month. Cancel anytime

The karma-ng-html2js-preprocessor plugin works by converting HTML files into JS strings and generates AngularJS modules that we load in step 3. Once loaded, AngularJS makes these modules available by putting the HTML files into the $templateCache. There are libraries available to help incorporate this into your project build process, for example using Grunt or Gulp. There is a popular example specifically for Gulp at https://github.com/miickel/gulp-angular-templatecache. Now that the template is available, we can access the HTML content using the compiled element we created in step 5.

In this recipe, we access the text content of the element using the find() method. Be aware that if using the smaller jQuery lite subset of jQuery, there are certain limitations compared to the full-blown jQuery version. The find() method in particular is limited to look up by tag name only. To read more about the find() method, visit the jQuery API documentation at http://api.jquery.com/find.

See also

  • The Starting with testing directives recipe

Searching elements using selectors

Directives, as you should know, attach special behavior to a DOM element. When AngularJS compiles and returns the element on which the directive is applied, it is wrapped by either jqLite or jQuery. This exposes an API on the element, offering many useful methods to query the element and its contents. In this recipe, you will learn how to use these methods to retrieve elements using selectors.

Getting ready

Follow the logic to define a beforeEach() function with the relevant logic to set up a directive as outlined in the Starting with testing directives recipe in this article. For this recipe, you can replicate the template that I suggested in the first recipe's There's more… section. For the purpose of this recipe, I tested against a property on scope named deejay:

var deejay = {
name: 'Shortee',
style: 'turntablism'
};

You can replace this with whatever code you have within the directive you're testing.

How to do it…

  1. First, create a basic test to establish that the HTML code contained within an h2
    tag is as we expected:
    it('should return an element using find()', function () 
    {});

  2. Next, retrieve a reference to the h2 tag using the find() method on the element providing the tag name as the selector:
    var h2 = element.find('h2');

  3. Finally, we create an expectation that the element is actually defined:
    expect(h2[0]).toBeDefined();

How it works…

In step 2, we use the find() method with the h2 selector to test against in step 3's expectation. Remember, the element returned is wrapped by jqLite or jQuery. Therefore, even if the element is not found, the object returned will have jQuery-specific properties; this means that we cannot run an expectation on the element alone being defined. A simple way to determine if the element itself is indeed defined is to access it via jQuery's internal array of DOM objects, typically the first. So, this is why in our recipe we run an expectation against element[0] as opposed to element itself.

There's more…

  • Here is an example using the querySelector() method. The querySelector() method is available on the actual DOM so we need to access it on an actual HTML element and not the jQuery wrapped element. The following code shows the selector we use in a CSS selector:
    it('should return an element using querySelector and css 
    selector', function() { var elementByClass = element[0].querySelector('.deejay-
    style'); expect(elementByClass).toBeDefined(); });

  • Here is a another example using the querySelector() method that uses an id selector:
    it(should return an element using querySelector and id selector', function() {
    var elementByClass = element[0].querySelector(' #deejay_name');
    expect(elementByClass).toBeDefined();
    });

    You can read more about the querySelector() method at https://developer.mozilla.org/en-US/docs/Web/API/document.querySelector.

See also

  • The Starting with testing directives recipe
  • The Accessing basic HTML content recipe

Accessing basic HTML content

A substantial number of directive tests will involve interacting with the HTML content within the rendered HTML template. This recipe will teach you how to test whether a directive's HTML content is as expected.

Getting ready

Follow the logic to define a beforeEach() function with the relevant logic to set up a directive as outlined in the Starting with testing directives recipe in this article. For this recipe, you can replicate the template that I suggested in the first recipe's There's more… section. For the purpose of this recipe, I will test against a property on a scope named deejay:

var deejay = {
name: 'Shortee',
style: 'turntablism'
};

You can replace this with whatever code you have within the directive you're testing.

How to do it…

  1. First, create a basic test to establish that the HTML code contained within a h2 tag is as we expected:
    it('should display correct deejay data in the DOM', 
    function () {});

  2. Next, retrieve a reference to the h2 tag using the find() method on the element providing the tag name as the selector:
    var h2 = element.find('h2');

  3. Finally, using the html() method on the returned element from step 2, we can get the HTML contents within an expectation that the h2 tag HTML code matches our scope's deejay name:
    expect(h2.html()).toBe(deejay.name);

How it works…

We made heavy use of the jQuery (or jqLite) library methods available for our element. In step 2, we use the find() method with the h2 selector. This returns a match for us to further utilize in step 3, in our expectation where we access the HTML contents of the element using the html() method this time (http://api.jquery.com/html/).

There's more…

We could also run a similar expectation for text within our h2 element using the text() method (http://api.jquery.com/text/) on the element, for example:

it('should retrieve text from <h2>', function() {
var h2 = element.find('h2');
expect(h2.text()).toBe(deejay.name);
});

See also

  • The Starting with testing directives recipe
  • The Searching elements using selectors recipe

Accessing repeater content

AngularJS facilitates generating repeated content with ease using the ngRepeat directive. In this recipe, we'll learn how to access and test repeated content.

Getting ready

Follow the logic to define a beforeEach() function with the relevant logic to set up a directive as outlined in the Starting with testing directives recipe in this article. For this recipe, you can replicate the template that I suggested in the first recipe's There's more… section. For the purpose of this recipe, I tested against a property on scope named breakers:

var breakers = [{
name: 'China Doll'
}, {
name: 'Crazy Legs'
}, {
name: 'Frosty Freeze'
}];

You can replace this with whatever code you have within the directive you're testing.

How to do it…

  1. First, create a basic test to establish that the HTML code contained within the h2 tag is as we expected:
    it('should display the correct breaker name', function () 
    {});

  2. Next, retrieve a reference to the li tag using the find() method on the element providing the tag name as the selector:
    var list = element.find('li');

  3. Finally, targeting the first element in the list, we retrieve the text content expecting it to match the first item in the breakers array:
    expect(list.eq(0).text()).toBe('China Doll');

How it works…

In step 2, the find() method using li as the selector will return all the list items. In step 3, using the eq() method (http://api.jquery.com/eq/) on the returned element from step 2, we can get the HTML contents at a specific index, zero in this particular case. As the returned object from the eq() method is a jQuery object, we can call the text() method, which immediately after that will return the text content of the element. We can then run an expectation that the first li tag text matches the first breaker within the scope array.

See also

  • The Starting with testing directives recipe
  • The Searching elements using selectors recipe
  • The Accessing basic HTML content recipe

Summary

In this article you have learned to focus on testing changes within a directive based on interaction from either UI events or application updates to the model. Directives are one of the important jewels of AngularJS and can range in complexity. They can provide the foundation to many aspects of the application and therefore require comprehensive tests.

Resources for Article:


Further resources on this subject: