How to test a directive having hardcoded AJAX call? - angularjs

I am not sure how can I test this directive, would someone provide a code snippet? Here is my directive:
.directive('checkUnique', ['$http', function($http) {
return {
require: 'ngModel',
link: function(scope, ele, attrs, c) {
var origonalVal = attrs.ignoreUniqueValue;
scope.$watch(attrs.ngModel, function() {
var toURL= 'to/an/api';
$http({
method: 'GET',
url: toURL
}).success(function(isUnique,status,headers,cfg) {
var iu = isUnique == 'true' ? true : false;
c.$setValidity('unique', iu);
}).error(function(data,status,headers,cfg) {
c.$setValidity('unique', false);
});
});
}
}
}])

First of all it is not a good idea to have this logic in the link function of your directive. Here a setup that I would use (simplified and not tested):
var myApp = angular.module('myApp', []);
myApp.factory('dataService', function($q, $http){
return {
isUnique: function(){
return $q(function(resolve, reject){
$http({
method: 'GET',
url: 'to/an/api'
}).success(function(isUnique,status,headers,cfg) {
resolve(isUnique == 'true');
}).error(function(data,status,headers,cfg) {
reject();
});
});
}
}
});
myApp.controller('UniqueController', function($scope, dataService){
var vm = this,
unWatchNgModel;
unWatchNgModel = $scope.$watch('ngModel', onNgModelChanged);
$scope.$on('$destroy', onDestroy);
function onNgModelChanged(){
dataService.isUnique().then(function(unique){
vm.ngModelCtrl.$setValidity('unique', unique);
});
}
function onDestroy(){
unWatchNgModel();
}
});
myApp.directive('checkUnique', ['$http', function($http) {
return {
require: ['checkUnique', 'ngModel'],
scope: {
ngModel: '='
}
controller: 'UniqueController',
controllerAs: 'unique',
bindToController: true
link: link
};
function link(scope, ele, attrs, ctrls) {
var checkUniqueCtrl = ctrls[0],
ngModelCtrl = ctrls[1];
checkUniqueCtrl.ngModelCtrl = ngModelCtrl;
}
}]);
To test this (the ajax part), use a setup like this:
// Note that you need the 'ngMockE2E' module to have access to the $httpBackend service.
describe('dataService', function() {
'use strict';
var dataService;
beforeEach(function() {
module('myApp');
inject(function($injector) {
dataService = $injector.get('dataService');
$httpBackend = $injector.get('$httpBackend');
});
});
describe('isUnique', function() {
it('should return true if the API returns true as value.', function() {
// Setup
var successSpy = jasmine.createSpy('success');
$httpBackend.expectGET(endpoint).respond(200, 'true');
// Execute
dataService.isUnique(successSpy);
$httpBackend.flush();
// Test
expect(successSpy).toHaveBeenCalledWith(true);
});
it('should return false if the API does not return true as value.', function() {
// Setup
var successSpy = jasmine.createSpy('success');
$httpBackend.expectGET(endpoint).respond(200, 'bogus');
// Execute
dataService.isUnique(successSpy);
$httpBackend.flush();
// Test
expect(successSpy).toHaveBeenCalledWith(false);
});
});
});

Related

AngularJS Directive data binding not happening from controller

I am facing the problem of data binding from controller to directive because of delay in response from the server.
To better understand just see a small program below.When I remove the timeout function, binding happens.
<msg track="{{customer}}"></msg>
angular.module('myApp').directive('msg', function() {
return {
scope: {
track :"#"
},
link : function(scope, element, attrs) {
},
template : "<a href>{{track}}</a>"
}
});
angular.module('myApp').controller('HomeCtrl', ['$scope', function($scope) {
setTimeout(function() {
$scope.customer = "shreyansh";
}, 5000);
// assume some service call inside controller
// service1.getData().then(function(data){$scope.customer = data})
}]);
How can i fix above problem so that above code should render as
<msg track="shreyansh" class="ng-isolate-scope">shreyansh</msg>.
Any help is appreciated.
Thanks
var app = angular.module('plunker', []);
app.factory('myService', function($http) {
var promise;
var myService = {
getData: function() {
if (!promise) {
promise = $http.get('test.json').then(function(response) {
return response.data;
});
}
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function(myService, $scope) {
myService.getData().then(function(d) {
$scope.data = d;
});
});
app.directive('msg', function() {
return {
restrict: 'E',
scope: {
track: "#"
},
link: function(scope, element, attrs) {
},
template: "<a href>{{track}}</a>"
}
});
<msg track="{{data.name}}"></msg>
test.json file
{
"name": "Pete"
}

Unit Testing a watch method call in Directive link function

I have the following Directive
.directive("myContainer", function (myContainerService,$window) {
return {
restrict: 'A',
templateUrl: "myContainer/MyContainer.tpl.html",
controller: 'myCtrl',
controllerAs: 'myCtrl',
scope: {
items: '='
},
link: function (scope, element) {
var timeout;
scope.$watch('myCtrl.items', function (items) {
var windowWidth = $window.innerWidth - 65;
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
myContainerService.saveItems(items);
}, 1000);
}, true);
}
};
})
And here is the Unit Test i have.
describe("myCtrl", function(){
var myCtrl;
var dirEle ;
var myScope;
// var to store the Mock Items
var myContainerService = $injector.get('myContainerService');
var items = [..]
beforeEach(inject(function($compile, $httpBackend){
$httpBackend.whenGET(/.*my-app\/restful-services\/items*./).respond({...});
scope.items = myContainerService.getItems();
dirEle = $compile('<div my-container items="items"></div>')(scope);
scope.$digest();
myScope = dirEle.isolateScope();
myCtrl = myScope.myCtrl;
}));
fit("Saving Items", inject(function($timeout){
spyOn(myContainerService, 'saveItems');
//$timeout.flush();
myScope.$digest();
$timeout.flush();
expect(myContainerService.saveItems).toHaveBeenCalledWith(myCtrl.items);
}));
});
And my test is failing as the saveItems is not getting called at all. Not sure what i am doing wrong.
Appreciate any inputs.
Thanks
You need to be using angulars $timeout that way in your test your $timeout.flush() will work:
.directive("myContainer", function (myContainerService,$window, $timeout) {
return {
restrict: 'A',
templateUrl: "myContainer/MyContainer.tpl.html",
controller: 'myCtrl',
controllerAs: 'myCtrl',
scope: {
items: '='
},
link: function (scope, element) {
var timeout;
scope.$watch('myCtrl.items', function (items) {
var windowWidth = $window.innerWidth - 65;
$timeout.cancel(timeout);
timeout = $timeout(function () {
myContainerService.saveItems(items);
}, 1000);
}, true);
}
};
})

Angular directive not rendering scope values in view

My directive uses a service which returns a promise, I need to display the scope attributes geoZip, geoCity and geoState in the template.
The issue is that those scope variables are not being shown in the template, I just see the comma.
What should I do to make it display the scope variables?
This is my directive code:
.directive('cityStateZip', function() {
return {
restrict: 'A',
transclude: true,
scope: {
zip: '=',
},
template: '<p>{{geoCity}}, {{geoState}} {{geoZip}}</p>',
controller: ['$scope', 'GeolocationService', function ($scope, GeolocationService) {
GeolocationService.geocode($scope.zip).then(function(result) {
if (result) {
console.log(result);
$scope.geoZip = result['address_components'][0]['long_name'];
$scope.geoCity = result['address_components'][1]['long_name'];
$scope.geoState = result['address_components'][2]['short_name'];
}
});
}]
};
})
.service('GeolocationService', ['underscore', '$q', function (underscore, $q) {
var gs = {};
gs.geocode = function(address) {
var geocoder = new google.maps.Geocoder();
var deferred = $q.defer();
geocoder.geocode( { "address": address }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK && results.length > 0) {
return deferred.resolve(underscore.first(results));
}
return deferred.reject();
});
return deferred.promise;
}
return gs;
}]);
I found that I have to use the $timeout service to make it work:
.directive('cityStateZip', function() {
return {
restrict: 'A',
transclude: true,
scope: {
zip: '=',
},
template: '<p>{{geoCity}}, {{geoState}} {{geoZip}}</p>',
controller: ['$scope', '$timeout', 'GeolocationService', function ($scope, $timeout, GeolocationService) {
GeolocationService.geocode($scope.zip).then(function(result) {
if (result) {
console.log(result);
$timeout(function() {
$scope.geoZip = result['address_components'][0]['long_name'];
$scope.geoCity = result['address_components'][1]['long_name'];
$scope.geoState = result['address_components'][2]['short_name'];
});
}
});
}]
};
})
Please let me know if there is best alternative (not using $timeout), Thanks!

How to mock service for angularjs directive's controller

I have a directive that i want to unit test. This works but i need to use the service in the controller. How do i go about this?
app.directive('myDirective', function() {
return {
restrict: 'EA',
scope: true,
templateUrl: 'my-directive.html',
controllerAs: 'md',
link:function (scope){
},
controller: function($scope, $element, $stateParams,service1) {
this.test = service1.testData;
}
}
});
And my unit test file so far
describe("Directive : my directive", function() {
var element, scope, controller, service1,stateParams;
beforeEach(module("app"));
beforeEach(module('src/templates/my-directive.html'));
beforeEach(function() {
service1 = {
method : {
name : "Test"
}
};
});
beforeEach(inject(function($compile, $rootScope ) {
scope = $rootScope.$new();
element = "<my-directive></my-directive>";
element = $compile(element)(scope);
scope.$digest();
controller = element.controller("myDirective");
}));
it("should do something to the scope", function() {
// test functions using service1
})
});
and my service
app.factory('service1', function(){
service1.testData = false;
service1.myPromise = function (t) {
var deferred = $q.defer();
$http.get("....")
.success(function(result) {
deferred.resolve(result)
})
.error(function(error) {
deferred.reject(error)
});
return deferred.promise;
}
});
How do i add my mocked service to my controller?
Before compiling the directive you can use the $provide service in a beforeEach block.
var service1;
beforeEach(module(function($provide) {
service1 = {
testData: 'Test data',
myPromise: function() {}
};
$provide.value('service1', service1);
}));
If your service method returns a promise, you can spy on the method and return a promise when the method is called.
var deferred;
beforeEach(inject(function($q) {
deferred = $q.defer();
spyOn(service1, 'myPromise').and.returnValue(deferred.promise);
}));
So later in your specs you can reject or resolve the promise.
deferred.resolve();
deferred.reject();

angularjs: test service call inside scope.$on

I have a directive as below:
app.directive('Directive', ['MyService', '$state',
function (MyService, $state) {
return {
restrict: 'E',
scope: {},
replace: true,
templateUrl: 'partials/sample.html',
link: function (scope, element, attrs) {
scope.$on('onEvent', function (event, result) {
MyService.getInfo($state.params.param1, result.data.value1, result.data.value2)
.then(function (response) {
scope.varX = response.data;
});
});
}
};
}]);
The test case written for this directive is as below:
it('MyDirective test case', function () {
mockBackend.whenGET('partials/sample.html').respond('<div>Test Template</div>');
var scope = rootScope.$new(),
element,
expectedResult = [
{
"data": "dummy"
}];
element = compile('<MyDirective></MyDirective>')(scope);
scope.$digest();
expect(scope.$broadcast).toHaveBeenCalledWith('onEvent', [{value1: 'val1', val2: 'val2'}]);
mockBackend.flush();
var testScope = element.isolateScope();
expect(testScope.varX).toEqual(expectedResult);
});
On running the test case, I get the below error:
Directive: appDirectives MyDirective test case FAILED
Expected undefined to equal [ { data : 'dummy' } ].
The testScope is not getting the var variable assigned.
What am I doing wrong in the test case?

Resources