Unit testing Angular directive with $http - angularjs

I have an Angular directive that, when attached to an <input>, waits one second after user input before querying an endpoint with $http. In short, it's meant to check username uniqueness.
It looks like this:
.directive('sgValidUsername', ['$http', function(http) {
var waitTimer;
var checkIfUserExists = function(e, ctrl) {
http.get('/publicapi/users/' + e.target.value + '/exists')
.success(function(data) {
ctrl.$setValidity('unique', !data.exists);
});
};
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
element.on('blur keyup', function(e) {
if (e.target.value) {
clearInterval(waitTimer);
waitTimer = setTimeout(function() {
checkIfUserExists(e, ctrl);
}, 1000);
}
});
}
};
}]);
I'm trying my best to write a good comprehensive Jasmine unit test suite, but it's not working out because I couldn't find an appropriate example to learn from. I end up reconstructing the directive in test form rather than actually testing the directive. Also, I get a 'no pending request to flush' error.
Any suggestions for the below?
describe('SignupForm directives', function () {
var form, // el to which directive is applied
$scope,
$httpBackend, // $http mock
usernameExistsHandler;
beforeEach(function() {
module('signupform.directives');
});
beforeEach(function() {
inject(function ($injector, $rootScope, $compile, $q, $timeout) {
$scope = $rootScope;
$httpBackend = $injector.get('$httpBackend');
usernameExistsHandler = $httpBackend.whenGET(/\/publicapi\/users\/.+?\/exists/);
var el = angular.element('<form name="form"><input type="text" name="username" ng-model="user.username" sg-username-is-valid /></form>');
$scope.user = { username: null };
$compile(el)($scope);
form = $scope.form;
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should invalidate with existing usernames', function() {
form.username.$setViewValue('username_in_use');
$scope.$digest();
expect($scope.user.username).toEqual('username_in_use');
usernameExistsHandler.respond('200', { exists: true });
$httpBackend.expectGET('/publicapi/users/' + $scope.user.username + '/exists/');
$httpBackend.flush();
expect(form.username.$valid).toBe(false);
});

Related

check attribute from html - angular directive testing jasmine karma

how to check attribute is present in HTML and match its value. this is a test spec.js I wrote,
define(['angular',
'angularMocks',
'site-config',
'ng_detector',
],
function(angular,
mock,
$app,
ng_detector) {
describe('ng-detector controller', function() {
beforeEach(angular.mock.module("webapp"));
var $compile, $rootScope, tpl, $scope, elm, templateAsHtml;
beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
// $scope = _$rootScope_.$new();
}));
it('should initialize the ng-detector directive', inject(function() {
var tpl = $compile("<div ng-detector ></div>")($rootScope);
$rootScope.$digest();
console.log(tpl) // Log: r{0: <div ng-detector="" class="ng-scope" ng-verison="1.6.4"></div>, length: 1}
templateAsHtml = tpl[0].outerHTML;
expect(templateAsHtml.attr('ng-version')).toEqual(angular.version.full);
}));
});
});
directive. that adds angular version to attribute ng-version
'use strict';
define(['app-module'], function(ng) {
$app.info('ng detector initialized. {file: directives/ng-detector.js}');
ng.directive('ngDetector', function() {
return {
restrict: "A",
link: function(scope, elm, attr) {
elm.attr('ng-version', angular.version.full);
}
};
});
return ng;
});
I want to get a ng-version attribute set by the directive and match the attribute value.
I figured out myself. I was looking at the different place.
it('should check the angular version number', angular.mock.inject(function() {
expect(tpl.attr('ng-version')).toEqual(angular.version.full);
}));

unit testing directive with link using controller

I'm trying to unit test my directive that set form validity depending on a controller variable.
My directive code :
angular.module('myModule',[])
.directive('myDirective', function() {
return {
restrict: 'A',
link: function(scope, element, attr, ctrl) {
scope.$watch("mailExist", function(){
if(scope.mailExist) {
ctrl.$setValidity('existingMailValidator', false);
} else {
ctrl.$setValidity('existingMailValidator', true);
}
});
}
};
});
When trying to unit test this directive, I'm trying to isolate the controller ctrl with this code:
describe('directive module unit test implementation', function() {
var $scope,
ctrl,
form;
beforeEach(module('myModule'));
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
var element =angular.element(
'<form name="testform">' +
'<input name="testinput" user-mail-check>' +
'</form>'
);
var ctrl = element.controller('userMailCheck');
$compile(element)($scope);
$scope.$digest();
form = $scope.testform;
}));
describe('userMailCheck directive test', function() {
it('should test initial state', function() {
expect(form.testinput.$valid).toBe(true);
});
});
});
Running this test, I still obtain:
Cannot read property '$setValidity' of undefined
that's mean I haven't really inject a controller.
What is wrong in my test?
Finally in found the solution:
first in code I have add :
require: 'ngModel',
and then modified the unit test as follow:
describe('directive module unit test implementation', function() {
var scope,
ngModel,
form;
beforeEach(module('myModule'));
beforeEach(inject(function($compile, $rootScope) {
scope = $rootScope.$new();
var element =angular.element(
'<form name="testform">' +
'<input name="testinput" ng-model="model" user-mail-check>' +
'</form>'
);
var input = $compile(element)(scope);
ngModel = input.controller('ngModel');
scope.$digest();
form = scope.testform;
}));
describe('userMailCheck directive test', function() {
it('should test initial state', function() {
expect(form.testinput.$valid).toBe(true);
});
});
});
and everything works fined.

How to write unit test case for scope and timeout function inside directive

I am using isolate scope in custom directive. I have updated plunker link. http://plnkr.co/edit/NBQqjxW8xvqMgfW9AVek?p=preview
Can someone help me in writing unit test case for script.js file.
script.js
var app = angular.module('app', [])
app.directive('myDirective', function($timeout) {
return {
restrict: 'A',
scope: {
content: '='
},
templateUrl: 'my-directive.html',
link: function(scope, element, attr) {
$timeout(function() {
element = element[0].querySelectorAll('div.outerDiv div.innerDiv3 p.myClass');
var height = element[0].offsetHeight;
if (height > 40) {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
}
})
scope.showMore = function() {
angular.element(element).removeClass('expandable');
scope.isShowMore = false;
};
scope.showLess = function() {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
};
}
}
})
(function() {
'use strict';
describe('Unit testing directive', function() {
var $compile, scope, element, compiledDirective, $rootScope, $timeout;
beforeEach(module("app"));
beforeEach(inject(function(_$compile_, _$rootScope_, _$timeout_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
$timeout = _$timeout_;
element = angular.element(' <div my-directive content="content"></div>');
compiledDirective = $compile(element)(scope);
scope.$digest();
}));
it('should apply template', function() {
expect(compiledDirective.html()).toBe('');
});
it('check for timeout', function() {
$timeout.flush();
});
});
})();
Use $timeout.flush() function for writing testcase for $timeout
it('check for timeout', function() {
scope.digest();
// flush timeout(s) for all code under test.
$timeout.flush();
// this will throw an exception if there are any pending timeouts.
$timeout.verifyNoPendingTasks();
expect(scope.isShowMore).toBeTruthy();
});
Check this article for better understanding.

Errors thrown when unit testing directive with Angular

I have a custom directive that uses a service. When I run my unit tests, I keep getting the thrown error 'Library does not exist on window'. How can I avoid getting that error in my Unit test?
example service
angular.module('example')
.factory('thirdParty', ['$window', function($window) {
if (typeof $window.thirdParty === 'undefined') {
throw new Error('Library does not exist on window');
} else {
return $window.thirdParty;
}
}]);
custom directive
angular.module('example')
.directive('customDirective', ['thirdParty',
function(thirdParty) {
var defaults, link;
link = function(scope, element, attrs, ctrls) {
// do something with thirdParty
};
return {
restrict: 'A',
require: 'ngModel',
link: link,
};
}]);
test
describe('customDirective', function() {
var element, compile, scope;
beforeEach(module('example'));
beforeEach(inject(function($compile, $rootScope) {
compile = $compile;
scope = $rootScope.$new();
}));
// Manually compile and link our directive
function getCompiledElement(template) {
var compiledElement;
var validTemplate = '<input ng-model="example.data" custom-directive />';
compiledElement = compile(template || validTemplate)(scope);
scope.$digest();
return compiledElement;
}
it('should do something', function() {
element = getCompiledElement();
// expects
});
});
You need to inject $window (stub that out too, for good measure) and stub/mock out the thirdParty lib.
var $window;
beforeEach(function () {
$window = {};
module('yourmodule', function ($provide) {
$provide.value('$window', $window);
});
$window.thirdParty = {};
});
it('throws', function () {
delete $window.thirdParty;
function fn () {
getCompiledElement();
}
expect(fn).to.throw(/does not exist on window/i);
});
it('just works™', function () {
function fn () {
getCompiledElement();
}
expect(fn).to.not.throw;
});

Angular Directive Behavior Testing Jasmine

Here's Plunker Link
Updated Plunker Updated Plunker
I have written a directive which hide and shows the div based on ajax call.
How i can i test a behaviour of Directive written ?Its shows the spinner on Ajax Call.
Spec
describe('loader directive', function() {
var $compile, scope;
beforeEach(module('plunker'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
}));
it('check if div shows on', function() {
var element = $compile('<div loading>Something</div>')(scope);
scope.$apply();
});
});
Directive
app.directive('loading', ['$http' ,function ($http)
{
return {
restrict: 'A',
link: function (scope, elm, attrs)
{
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (v) {
if(v) {
elm.show();
} else {
elm.hide();
}
});
}
};
}]);
You can just check for an .is(':visible').
describe('loader directive', function() {
var $compile, scope, $httpBackend, $http, element;
beforeEach(module('plunker'));
beforeEach(inject(function(_$compile_, _$rootScope_, _$httpBackend_, _$http_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', '/test').respond(200, {
test: true
});
$http = _$http_;
}));
afterEach(function() {
$httpBackend.flush();
if (element && element.length) {
element.remove();
}
});
it('check if div shows on http call', function() {
element = $compile('<div class="loading-bar" loading>Something</div>')(scope);
element.appendTo('body');
$http.get('/test');
scope.$apply();
expect(element.is(':visible')).toBe(true);
});
});
To mock out the $http calls you will need to use $httpBackend service.
Plunker Demo

Resources