Search code examples
angularjsunit-testingmocha.jskarma-runnerbrowserify

Proper way of testing directives


Let's say I've built very simple directive:

moment = require 'moment'   # as you can see I'm using browserify

###  whenever mouse hovered, it shows relative time ###
app.directive 'simpleDirective',->
   restrict: 'E'
   scope: date:'='
   template: "<div class='nice-date' date='date' ng-mouseenter='hover = true' 
                        ng-mouseleave='hover = false'>{{niceDate}}</div>"
   controller: ($scope)->
       $scope.$watch 'hover',->
          $scope.niceDate = if $scope.hover 
             moment($scope.date).fromNow()
          else ''

Now, I can easily test if directive properly compiles, using test like this:

describe 'simpleDirective test', ->
    $compile = $rootScope = undefined
    beforeEach module('myApp')

    beforeEach inject (_$compile_, _$rootScope_) ->
      $compile = _$compile_
      $rootScope = _$rootScope_

    it 'Replaces the element with the appropriate content', ->
        var element = $compile("<simple-directive date='mydate' />")($rootScope)
        $rootScope.$digest()
        expect(element.html()).toContain("class='nice-date'")

Now, how can I test the behavior of mouseenter? I mean first of all I have no idea how to get to the guts of the controller. And also I have no idea how to access moment.js from tests (I'm using karma with mocha).

Should I turn moment.js thing into ng-service dependency? Then it means every single thing used I'm gonna have to import as an angular service? Or maybe I just use browserify on my tests? Then every single spec file has to be browserified independently, because there's no single entry point for tests.


Solution

  • First, you have to add moment.js as a karma dependency in your configuration file (usually named karma.conf.js... at least for Node projects anyway). That way you'll have access to it from the tests. Second, this is how I would write the tests:

    describe('simlpleDirective', function() {
      var $scope, $compile;
      beforeEach(module('myApp'));
    
      beforeEach(inject($rootScope, _$compile_) {
        // Always create a new scope to prevent permanently dirtying the root scope for other tests
        $scope = $rootScope.$new();
        $compile = _$compile_
      });
    
      it('Replaces element with content or something like that', function() {
        // Abstract the date so that you can test it later
        var date = new Date(),
          element;
    
        $scope.mydate = date;
    
        $scope.$apply(function() {
          element = $compile("<simple-directive date='mydate'></simple-directive>")($scope);
        });
    
        // You have full access to the compiled element here
        expect(element.find('.nice-date').text()).toBe('');
    
        // You also have access to $scope variables here
        expect($scope.niceDate).toBe('');
    
        // You should probably wrap the hover stuff in another test, but I'm being lazy here
        formattedDate = moment(date).fromNow();
        // Manually trigger the hover event
        element.find('.nice-date').triggerHandler('mouseenter');
    
        // Now check one or both again
        // $scope.$apply or $scope.$digest might be necessary here
        expect(element.find('.nice-date').text()).toBe(formattedDate);
        expect($scope.niceDate).toBe(formattedDate);
      });
    });
    

    Hopefully that helps. NOTE that this code has not been tested so it might need some tweaks to suit your needs.

    NOTE 2 I just noticed that I also wrote this test in Jasmine. Let me know if it's not easy enough to understand.