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.
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.