---
title: Testing Directives with Dependencies in Angular
teaser: In which we learn about how to effectively override directives in tests.
tags: web,javascript,angularjs,testing,coffeescript
author: Elliot Winkler
published_on: 2015-01-20
---

Testing Angular applications is multifaceted. On one hand, it's fantastic that
the Angular team places such a high emphasis on testing and provides tools to
stub out various components of your application so that they can be tested in
isolation. On the other hand, writing these tests is sometimes difficult, as the
tools aren't well documented, and different components are stubbed out or tested
using different APIs that seem to elude memorization. In this post we'll talk
about one case that can be particularly frustrating to test: directives with
dependencies.

Let's say we have a `group-activity-checkbox` directive. This directive is
unique in that it must be present within an `activity-feed` directive. The body
of the directive is kept simple for illustration purposes:

```coffee
groupActivityCheckboxDirective = ->
  restrict: 'E'
  require: '^activityFeed'

  link: (scope, element, attrs, activityFeedCtrl) ->
    activityFeedCtrl.updateActivities()

angular.module('socialnetwork')
  .directive('groupActivityCheckbox', [
    groupActivityCheckboxDirective
  ])
```

Now we want to write some tests for this directive. Generally directive tests
follow this pattern:

1. Stub any dependencies of the directive
1. Compile the directive as an element
1. Set something on the element, trigger an event, set something on the scope,
   etc., if necessary
1. Make an assertion on the element's attributes or content, or on a dependency
   being accessed in some way

In order to stub the dependencies on our `group-activity-checkbox` directive, we
first need to identify those dependencies. You may think that we only have one
-- the `activity-feed` directive -- but in fact we have two, because the
`require` option will cause Angular to look for an ActivityFeedCtrl as well.

Given this, here's what the pattern would look like for our directive:

1. Stub `activity-feed` and ActivityFeedCtrl
1. Compile `group-activity-checkbox` in the context of `activity-feed`
1. Assert that `updateActivities()` on the ActivityFeedCtrl instance is
   called

Now we can start writing the tests:

```coffee
describe '<group-activity-checkbox>', ->
  [$compile, $scope] = []

  beforeEach ->
    module 'socialnetwork', ->
      # Stub ActivityFeedCtrl
      # Stub <activity-feed>
      return

    inject (_$compile_, $rootScope) ->
      $compile = _$compile_
      $scope = $rootScope.$new()

  compileDirective = ->
    # Use the $compile service to compile <group-activity-checkbox> in
    #   the context of <activity-feed>, then return the element

  it 'calls ActivityFeedCtrl#updateActivities', ->
    # Use compileDirective to compile <group-activity-checkbox>
    # Assert that updateActivities on our fake ActivityFeedCtrl is
    #  called
```

Notice that we've left out some pieces above. Let's go about filling them in
now.

### Stubbing ActivityFeedCtrl

Angular gives us a way to register controllers using
[`$controllerProvider.register`][controller-provider-docs]. Re-registering a
controller results in overriding it. Given this, here's how we'd stub
ActivityFeedCtrl:

```coffee
[ActivityFeedCtrl] = []

beforeEach ->
  class ActivityFeedCtrl
    updateActivities: ->

  module 'socialnetwork', ($controllerProvider) ->
    $controllerProvider.register 'ActivityFeedCtrl', ActivityFeedCtrl
```

### Stubbing `<activity-feed>`

Unfortunately, stubbing directives that are dependencies of other directives
isn't as straightforward as it seems.

Angular also gives us a way to register directives using
[`$compileProvider.directive`] [directive-docs]. This is the method called when
you register a directive with the `angular.module(...).directive(...)` syntax,
and you can technically use it directly in tests, but doing so will lead to
frustration. One might think that, since `$controllerProvider.register` and
other tools override a given component, `$compileProvider.directive` works the
same way. But this isn't true.

If we take a [closer look][directive-source] at this method, here's what we
learn:

* A directive may have more than one instance. Calling
  `$compileProvider.directive` twice with the same name but different factories
  results in two instances of that directive being registered.
* The first time you register a directive, Angular will register a corresponding
  service, named after the directive and suffixed with the word "Directive".
  This service (a factory function) returns all of the instances of the
  directive in the form of configuration objects (which are then used later by
  Angular to compile the directive).
* Angular expects these configuration objects to be normalized. For instance, if
  the `priority` of the directive has not been specified, it needs
  to default to 0, and if a `controller` has been specified and `require` has
  not been specified, `require` must be set to the name of the directive itself.

Given this information, let's make a helper function that will allow us to
*really* override a directive. Note that we've simplified the logic in
`$compileProvider.directive` -- completing it is left as an exercise for the
reader -- but it suits our needs just fine right now:

```coffee
overrideDirective = ($provide, name, options = {}) ->
  serviceName = name + 'Directive'
  $provide.factory serviceName, ->
    directive = angular.copy(options)
    directive.priority ?= 0
    directive.name = name
    if !directive.require? && directive.controller?
      directive.require = directive.name
    [directive]
```

Now here's how we'd use this function:

```coffee
module 'socialnetwork', ($provide) ->
  overrideDirective $provide, 'activityFeed',
    restrict: 'E'
    controller: 'ActivityFeedCtrl'
    template: '<group-activity-checkbox>'
  return
```

### Filling in the remaining pieces

To complete our tests, we need to ensure that `compileDirective` returns the
element that represents the `group-activity-checkbox` directive. Because
`group-activity-checkbox` has to be compiled in the context of `activity-feed`,
we first compile `activity-feed` and then pull `group-activity-checkbox` out of
it. Then, we write the test itself, making use of `compileDirective`.

Here is what our finished tests looks like:

```coffee
overrideDirective = ($provide, name, options = {}) ->
  serviceName = name + 'Directive'
  $provide.factory serviceName, ->
    directive = angular.copy(options)
    directive.priority ?= 0
    directive.name = name
    if !directive.require? && directive.controller?
      directive.require = directive.name
    [directive]

describe '<group-activity-checkbox>', ->
  [$compile, $scope, ActivityFeedCtrl] = []

  beforeEach ->
    class ActivityFeedCtrl
      updateActivities: ->

    module 'socialnetwork', ($controllerProvider, $provide) ->
      $controllerProvider.register 'ActivityFeedCtrl', ActivityFeedCtrl
      overrideDirective $provide, 'activityFeed',
        restrict: 'E'
        controller: 'ActivityFeedCtrl'
        template: '<group-activity-checkbox>'
      return

    inject (_$compile_, $rootScope) ->
      $compile = _$compile_
      $scope = $rootScope.$new()

  compileDirective = ->
    parentElement = $compile('<activity-feed>')($scope)
    parentElement.children().eq(0)

  it 'calls ActivityFeedCtrl#updateActivities', ->
    spyOn(ActivityFeedCtrl.prototype, 'updateActivities')
    compileDirective()
    expect(ActivityFeedCtrl.prototype.updateActivities).toHaveBeenCalled()
```

(For an interactive version, see the [plunker][plunker].)

[controller-provider-docs]: https://docs.angularjs.org/api/ng/provider/$controllerProvider
[directive-docs]: https://docs.angularjs.org/api/ng/provider/$compileProvider
[directive-source]: https://github.com/angular/angular.js/blob/v1.3.9/src/ng/compile.js#L756
[plunker]: http://plnkr.co/edit/Y2COp3
