I've been trying testing if a callback have been called on my controller.
Controller
(function () {
'use strict';
angular
.module('GeoDashboard')
.controller('CiudadCtrl', CiudadCtrl);
CiudadCtrl.$inject = ['$scope', '$interval', '$rootScope','Ciudad'];
function CiudadCtrl($scope, $interval, $rootScope, Ciudad) {
$scope.refreshCiudad = refreshCiudad;
$scope.refreshClima = refreshClima;
var removeListenerRefreshCiudad = $scope.$on('refreshCiudad', refreshCiudad);
var cancelIntervalIdClima = $interval(refreshClima, 600000);
function refreshCiudad() {
//...
}
}
})();
Testing
beforeEach(inject(eachSpec));
function eachSpec($rootScope, $controller, $httpBackend, $interval, _Ciudad_){
rootScope = $rootScope;
scope = $rootScope.$new();
httpBackend = $httpBackend;
interval = sinon.spy($interval);
CodificacionGeograficaService = _CodificacionGeografica_;
CiudadService = _Ciudad_;
CiudadController = $controller('CiudadCtrl', {
$scope : scope,
$interval: interval,
Ciudad: CiudadService
});
}
it('3. DeberÃa llamar a "refreshCiudad" al escuchar el evento "refreshCiudad"', spec3);
function spec3(){
sinon.spy(scope, 'refreshCiudad');
rootScope.$broadcast('refreshCiudad');
expect(scope.refreshCiudad).toHaveBeenCalled();
scope.refreshCiudad.restore();
}
But when I trying to emit a $broadcast event, the callback is not have been called.
What i've doing wrong?
Finally This is working, but I don't understand why.
The solution was wrap the callback function for $scope.$on in another function like this
function CiudadCtrl($scope, $interval, $rootScope, Ciudad) {
$scope.refreshCiudad = refreshCiudad;
//instead this
//var removeListenerRefreshCiudad = $scope.$on('refreshCiudad', refreshCiudad);
//I set the callback like this
var removeListenerRefreshCiudad = $scope.$on('refreshCiudad', function(){
$scope.refreshCiudad(); //And this work!
});
function refreshCiudad() {
//...
}
}
Related
This is a relatively simple piece of code that calls a service and returns some data. I need to set the $scope with the result of the data. Is there an easy way to set this data to the scope without resorting to to binding the scope to the function in the then clause?
Angular Code
(function () {
var app = angular.module('reports', []);
var reportService = function($http, $q) {
var service = {};
service.getMenuData = function() {
var deffered = $q.defer();
$http.get('/Report/MenuData').success(function(data) {
deffered.resolve(data);
}).error(function(data) {
deferred.reject("Error getting data");
});
return deffered.promise;
}
return service;
};
reportService.$inject = ['$http', '$q'];
app.factory('reportService', reportService);
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = function(e) {
reportService.getMenuData().then(function(data) {
// Need to set the $scope in here
// However, the '$scope' is out of scope
});
}
};
reportMenuController.$inject = ['$scope', '$http', 'reportService'];
app.controller('ReportMenuController', reportMenuController);
})();
Markup
<div>
<div ng-controller="ReportMenuController">
<button ng-click="getMenuData()">Load Data</button>
</div>
</div>
There is absolutely no problem to set the $scope from within the function passed to then(). The variable is available from the enclosing scope and you can set your menu data to one of its fields.
By the way: You should consider to use then() instead of success() for your http request. The code looks much nicer because then() returns a promise:
service.getMenuData = function() {
return $http.get('/Report/MenuData').then(function(response) {
return response.data;
}, function(response) {
deferred.reject("Error getting data");
});
}
success() is deprecated by now.
I didn't notice the small detail missing in the plunker where my code was different.
(function () {
...
var reportMenuController =
function ($scope, $http, reportService) {
$scope.getMenuData = getMenuData;
function getMenuData(e) {
reportService.getMenuData().then(function(data) {
// Now I have access to $scope
});
}
};
...
})();
Notice the changes to the two lines as below:
$scope.getMenuData = getMenuData;
function getMenuData(e) {
This also begs a small question which is, "Why is it okay to set getMenuData to the $scope before it is declared?
I have a UploadDocumentCtrl.js where i am accessing a service call to a function inside another function . This outer function is bound to the scope . My problem is i am unable to access the code statements inside this (Code block C) . I want to access the 'flag' variable and check if that is true . Can anyone point me in the correct direction or tell me what i am doing wrong here ? Thanks ..
UploadDocumentsCtrl.js
(function () {
'use strict';
angular.module('myApp')
.controller('UploadDocumentsCtrl', UploadDocumentsCtrl);
UploadDocumentsCtrl.$inject = ['$rootScope', '$scope', '$modalInstance', '$window', 'companyService'];
function UploadDocumentsCtrl($rootScope, $scope, $modalInstance, $window, companyService) {
$scope.onFileSelect = onFileSelect;
$scope.buttonDisabled = false;
function onFileSelect(files) {
//Can access the code here
function upload(file) {
//Can access the code here as well
companyService.uploadDocuments(file)
.success(function (data, status, headers, config) {
// Code block C
// Cannot access any code here or the error code block below
$scope.flag = true;
})
.error(function (data, status, headers, config) {
});
}
files.forEach(function (file) {
file.progress = 0;
file.percent = 0;
$scope.filesToUpload.push(file);
upload(file);
});
}
}
})();
Jasmine test case
(function () {
"use strict";
describe('UploadDocumentsCtrl', function () {
var scope, companyService,companyControllerFactory;
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function($rootScope, $controller , _companyService_) {
companyService = _companyService_;
scope = $rootScope.$new();
companyControllerFactory = function(){$controller('UploadDocumentsCtrl',
{
$scope: scope,
companyService: _companyService_
});
};
}));
describe("onFileSelect", function() {
it(" Should make the flag to true ", function() {
var files = [{}];
companyControllerFactory();
spyOn(companyService, 'uploadDocuments').and.returnValue({ success: function(){}});
scope.onFileSelect(files);
expect(scope.flag).toBe(true);
});
});
});
})();
The error i am getting while trying to do the above..
1) Should make the flag to true
UploadDocumentsCtrl onFileSelect
TypeError: companyService.uploadDocuments(...).success(...) is undefined in http://localhost:9876/absoluteC:/Users
/Documents/fle/Fle/WebApiRole/app/company/UploadDocumentsCtrl.js?f11d5dcacbf2ca1d63778bfa04c582862e325523
( line 31)
upload#http://localhost:9876/absoluteC:/Users/Documents/fle/Fle/WebApiRole/app/company/UploadDocumentsCtrl
.js?f11d5dcacbf2ca1d63778bfa04c582862e325523:31:17
onFileSelect/<#http://localhost:9876/absoluteC:/Users/Documents/fle/Fle/WebApiRole/app/company/UploadDocum
entsCtrl.js?f11d5dcacbf2ca1d63778bfa04c582862e325523:51:17
onFileSelect#http://localhost:9876/absoluteC:/Users/Documents/fle/Fle/WebApiRole/app/company/UploadDocumen
tsCtrl.js?f11d5dcacbf2ca1d63778bfa04c582862e325523:46:13
#http://localhost:9876/base/test/company/UploadDocumentsCtrlSpec.js?c5db561e203bdfae1a6f7509347d3f7032e8f785:35:17
In my project I am using below format to spy service. You can try below option:
var deffered = q.defer();
spyOn(companyService, 'uploadDocuments').and.returnValue(deffered.promise);
deffered.resolve();
And to apply this you will have to use $rootScope.$apply() before assert (i.e. before expect())
Is companyService making an http call?
If so, you'll need to mock the response with $httpBackend and then get to the proper conditions based on the mocked response using $httpBackend.flush().
I want to use single_video variable in outside of the controller function. It prints well in first console log. However it gives a single_video is undefined error in second console.log which is outside of the controller function. Because of the asynchronousity.
var single_video;
var app = angular.module('myApp', []);
app.controller('randomVideo', function($scope, $http) {
var onSuccess = function(response){
$scope.video = response.data;
single_video = $scope.video;
//First console.log
console.log('1st ID='+single_video.yt_id);
};
var onError = function(reason){
$scope.error = "There is an error about getting random_video.php";
};
$http.get("http://www.ytmdb.com/ytvideo/api/random_video.php")
.then(onSuccess, onError);
});
//Second console.log
console.log('2nd ID='+single_video.yt_id);
You can create a function that you invoke inside the success callback passing your variable as a parameter to the function:
var app = angular.module('myApp', []);
app.controller('randomVideo', function($scope, $http) {
var onSuccess = function(response){
$scope.video = response.data;
single_video = $scope.video;
//First console.log
console.log('1st ID='+single_video.yt_id);
test(single_video.yt_id);
};
var onError = function(reason){
$scope.error = "There is an error about getting random_video.php";
};
$http.get("http://www.ytmdb.com/ytvideo/api/random_video.php")
.then(onSuccess, onError);
});
function test(a)
{
console.log('2nd ID='+a);
}
Define your global variables in a factory service and inject the service in your angular controller where ever you need. (Note: try to put everything in an IIFE)
Global Variables using services, here is an example:
var myApp = angular.module('myApp',[]);
myApp.factory('MySvc', function() {
return {
name : 'your name'
};
});
and in a controller:
function MyCtrl($scope, MySvc) {
$scope.name = MySvc.name;
}
I would like to know how to test some Angular scope variables at my controller that was created inside an ajax request.
What I mean is... This is my controller:
app.controller('NewQuestionCtrl', function ($scope, Question) {
loadJsonAndSetScopeVariables($scope, Question);
});
function loadJsonAndSetScopeVariables(scope, Question) {
Question.loadJson().then(function(success) {
var result = success.data.variables;
scope.levels = result.levels;
scope.tags = result.tags;
scope.difficulties = result.difficulties;
scope.questionTypes = result.questionTypes;
scope.areas = result.areas;
},function(data){
});
}
One of the prerequisites is not to use mock.
At my test I was able to inject my Question service:
describe('Controller: NewQuestionCtrl', function () {
beforeEach(angular.mock.module('testmeApp'));
var NewQuestionCtrl, scope, QuestionService;
beforeEach(inject(function ($controller, $rootScope, Question) {
scope = $rootScope.$new();
QuestionService = Question;
NewQuestionCtrl = $controller('NewQuestionCtrl', {
$scope: scope
});
}));
it('should attach a list of areas to the scope', function (done) {
expect(scope.areas).toBeDefined();
done();
});
Please, someone could help me?
Create a mock for Question and use that. There are several ways to do this. This is just one of them.
You could alternatively inject a real instance of Question and spy on that instead, but a mock is preferred to isolate these unit tests from the Question unit tests.
var questionDeferred, myController, scope;
var mockQuestion = {
loadJson: angular.noop
};
beforeEach(inject(function($q, $rootScope, $controller) {
questionDeferred = $q.defer();
scope = $rootScope.$new();
spyOn(mockQuestion, 'loadJson').and.returnValue(questionDeferred);
// Because your function is run straight away, you'll need to create
// your controller in this way in order to spy on Question.loadJson()
myController = $controller('NewQuestionCtrl', {
$scope: scope,
Question: mockQuestion
});
}));
it('should attach a list of areas to the scope', function (done) {
questionDeferred.resolve({/*some data*/});
scope.$digest();
expect(scope.areas).toBeDefined();
done();
});
I have a directive which uses a Service, calls a method of the service which returns a promise, and does work modifying the DOM inside the subsquent 'then' (myTestDirective below).
I'm trying to unit test this directive and when I run the test nothing inside of the 'then' is being called: the promise is rejected or the resolution not propagated?
I followed all the steps in this post to setup my unit test
When I load the directive in the browser I see both messages, OUTSIDE D3 then INSIDE D3, as you'd expect.
However in the unit test the element is updated only with the first message, like so:
<my-test-directive>***OUTSIDE D3***</my-test-directive> .
In the browser I see both messages.
Does anybody know what is going on here, do I need to inject mock or spyOn something? Is this an async problem where the test runs before script tag finished loading? I see the unit test accessing d3.v3.js, so it appears the script tag happens. I have also unit tested the d3Service on it's own, and it worked. Once in a while I actually see the correct results without changing the test at all.
I see clues in this question but unable to understand how to apply it in my situation: Angularjs promise not being resolved in unit test
Here is the code:
d3Service:
var d3 = angular.module('d3', []);
d3.factory('d3Service', ['$document', '$q', '$rootScope', '$window',
function($document, $q, $rootScope, $window) {
var d = $q.defer();
function onScriptLoad() {
$rootScope.$apply(function() { d.resolve(window.d3); });
}
var scriptTag = $document[0].createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.async = true;
scriptTag.src = 'lib/d3.v3.js';
scriptTag.onreadystatechange = function () {
if (this.readyState == 'complete') onScriptLoad();
}
scriptTag.onload = onScriptLoad;
var s = $document[0].getElementsByTagName('body')[0];
s.appendChild(scriptTag);
return {
d3: function() { return d.promise; }
};
}]);
Directive
var myDirectives = angular.module('myDirectives', ['d3']);
myDirectives.directive('myTestDirective', ['d3Service', '$window',
function(d3Service, $window) {
return {
restrict: 'EA',
link: function(scope, ele, attrs) {
var f = angular.element(ele[0])
f.append('**OUTSIDE D3***')
d3Service.d3().then(function(d3){ // Nothing here runs at all.
var e = angular.element(ele[0]) // In browser it works, but
e.append('***INSIDE D3***') // not in the unit test.
})
}
}
}])
Unit Test
describe('Test Directive', function(){
var $scope, elem, compiled, html;
beforeEach(function (){
module('myDirectives');
html = '<my-test-directive></my-test-directive>';
inject(function($compile, $rootScope) {
$scope = $rootScope;
elem = angular.element(html);
compiled = $compile(elem)($scope);
$scope.$digest();
});
});
it('should create an svg element with its data', function(){
console.log(elem) //outputs the element with only ***OUTSIDE D3***
})
})
Thanks for any tips or information!!!!!
What I did was load d3.v3.js in my karma.conf and then create mockd3Service in unit test that return a promise. If anybody know better solution let me know.
Here is new unit test that is working:
describe('d3 Directives', function(){
var $compile, $rootScope, $window, mockd3Service, $q, html, element, data;
//setup test
beforeEach(function(){
mockd3Service = {}
module('myDirectives')
module(function($provide){
$provide.value('d3Service', mockd3Service)
})
inject(function(_$compile_, _$rootScope_, _$window_, _$q_) {
$window = _$window_;
$compile = _$compile_;
$rootScope = _$rootScope_;
$q = _$q_
});
mockd3Service.d3 = function() {
var deferred = $q.defer();
deferred.resolve($window.d3)
return deferred.promise;
}
});
//run test
it('make test', function(){
html = '<my-directive data="testData"></my-directive>'
element = angular.element(html)
element = $compile(html)($rootScope)
$rootScope.testData = somedata
$rootScope.$digest();
expect(element...)
})
})