angular/jasmine: Error: Expected spy to have been called - javascript

The code exists here https://review.openstack.org/#/c/418828/ but I will go into more detail below:
I am writing tests for this particular piece of code: https://review.openstack.org/#/c/418828/26/openstack_dashboard/static/app/core/network_qos/qos.service.js
(function() {
"use strict";
angular.module('horizon.app.core.network_qos')
.factory('horizon.app.core.network_qos.service', qosService);
qosService.$inject = [
'$filter',
'horizon.app.core.openstack-service-api.neutron',
'horizon.app.core.openstack-service-api.userSession'
];
/*
* #ngdoc factory
* #name horizon.app.core.network_qos.service
*
* #description
* This service provides functions that are used through the QoS
* features. These are primarily used in the module registrations
* but do not need to be restricted to such use. Each exposed function
* is documented below.
*/
function qosService($filter, neutron, userSession) {
var version;
return {
getPolicyPromise: getPolicyPromise,
getPoliciesPromise: getPoliciesPromise
};
/*
* #ngdoc function
* #name getPoliciesPromise
* #description
* Given filter/query parameters, returns a promise for the matching
* policies. This is used in displaying lists of policies. In this case,
* we need to modify the API's response by adding a composite value called
* 'trackBy' to assist the display mechanism when updating rows.
*/
function getPoliciesPromise(params) {
return userSession.get().then(getQoSPolicies);
function getQoSPolicies() {
return neutron.getQoSPolicies(params).then(modifyResponse);
}
function modifyResponse(response) {
return {data: {items: response.data.items.map(modifyQos)}};
function modifyQos(policy) {
policy.trackBy = policy.id;
policy.apiVersion = version;
policy.name = policy.name || policy.id;
return policy;
}
}
}
/*
* #ngdoc function
* #name getPolicyPromise
* #description
* Given an id, returns a promise for the policy data.
*/
function getPolicyPromise(identifier) {
neutron.getVersion().then(setVersion);
return neutron.getQosPolicy(identifier).then(modifyResponse);
function modifyResponse(response) {
response.data.apiVersion = version;
return {data: response.data};
}
}
}
})();
This is my current test file:
(function() {
"use strict";
describe('qosService', function() {
var service;
beforeEach(module('horizon.app.core'));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.app.core.network_qos.service');
}));
describe('getPoliciesPromise', function() {
it("provides a promise that gets translated", inject(function($q, $injector, $timeout) {
var neutron = $injector.get('horizon.app.core.openstack-service-api.neutron');
var session = $injector.get('horizon.app.core.openstack-service-api.userSession');
var deferred = $q.defer();
var deferredSession = $q.defer();
spyOn(neutron, 'getQoSPolicies').and.returnValue(deferred.promise);
spyOn(session, 'get').and.returnValue(deferredSession.promise);
var result = service.getPoliciesPromise({});
deferred.resolve({
data: {
items: [{id: 123, name: 'policy1'}]
}
});
$timeout.flush();
expect(neutron.getQoSPolicies).toHaveBeenCalled();
expect(result.$$state.value.data.items[0].name).toBe('policy1');
}));
});
});
})();
When I run the tests I currently get errors saying:
Expected spy getQoSPolicies to have been called.
As you can see, getQoSPolicies is definitely called. If anyone can see what is wrong with the tests to give me that error it would be much appreciated!! Many thanks in advance!

You should be resolving following promise (deferredSession) along with neutron one, or it won't go inside .then of userSession.get().then(getQoSPolicies):
var deferredSession = $q.defer();
spyOn(session, 'get').and.returnValue(deferredSession.promise);
...
...
deferredSession.resolve({});
deferred.resolve(...);
$timeout.flush();
Resolve that along with the existing and it should work as you expect!

Related

AngularJS how can I call functions properly, outside of the function

I am new to AngularJS and am trying to write a code to extract a data from JSON file.
I wrote a GET function and now want to call the GET function outside of the function.
I have a getData function and on the last line, there is var questions = getData'~~~'. I think this is wrong in my code. How can I call the getData function out side of the DataFactory function.
(function(){
angular
.module("GrammarQuiz")
.factory("DataService", DataFactory);
function DataFactory($log, $http){
var vm = this
var dataObj = {
questions: questions
};
vm.sort = sort;
vm.random = random;
vm.getData = getData;
var temp = 0;
// right now I have questions variable here
// but I want to move this to the outside of the function
//var questions = getData('data1.json');
function getData(apicall){
$log.log('begin!!!');
$http.get('api/' + apicall,
{headers:
{token: 'check!'}
}
).then(function(response){
$log.log(response.data);
questions = response.data;
}, function(response){
$log.log(response.data || "Request failed");
});
}
function sort(array) {
return array.sort(function() {
return .5 - Math.random();
});
}
function random() {
for (var key in dataObj.questions) {
dataObj.questions[key].Choices = sort(dataObj.questions[key].Choices);
}
}
random();
return dataObj;
}
var questions = DataFactory.getData('data1.json');
})();
As I mentioned in my comment, you need to inject your service into your controller. Something like this works:
(function(){
var myApp = angular.module('myApp',[]);
angular
.module("myApp")
.controller("MyCtrl", MyCtrl);
MyCtrl.$inject = ["myApp.myService"]; //injects your service into your controller
function MyCtrl(dataservice) {
var vm = this;
vm.name = 'Superhero';
//calls the service
dataservice.getData();
}
angular.module("myApp").factory("myApp.myService", function() {
//exposes the service's methods
//you need this, vs the vm syntax in your service
var service = {
getData: getData
};
return service;
function getData(){
alert("S");
}
});
})();
JSFiddle: http://jsfiddle.net/Lvc0u55v/8234/
You need to make your api calls in a 'Factory' or 'Services' file. Then make a call to the
'get' method in the Factory file in the 'Controller' file. Code separation is necessary, so
take advantage of the Factories and Controllers.
Refer to example below :
# user.factory.js
# 'app.foo.user' refers to your directory structure i.e. app/foo/user/user.factory.js
(function() {
'use strict';
angular
.module('app.foo.user', [])
.factory('userSvc', UserService);
/* #ngInject */
function UserService(
$log,
$q,
$http,
$window,
$state,
logger,
session,
utils,
API_CONFIG) {
var ENDPOINTS = {
USERS: '/v1/users'
};
/**
* #ngdoc service
* #name app.foo.user
* #description User management
*/
var service = {
get: get
};
/**
* #ngdoc method
* #name get
* #description Returns all users
* #methodOf app.foo.user
* #returms {promise} user or null if not found
*/
function get() {
var q = $q.defer();
var request = utils.buildAuthRequest( session, 'GET', ENDPOINTS.USERS );
$http(request)
.success(function (users) {
q.resolve(users.result);
})
.error(function (error) {
logger.error('UserService.get > Error ', error);
return q.promise;
}
}
})();
//------------------------------------------------------------------------------------
# user.module.js
# 'app.foo.user' refers to your directory structure i.e. app/foo/user/user.module.js
(function() {
'use strict';
angular
.module('app.foo.user', [
]);
})();
//------------------------------------------------------------------------------------
# user-list.controller.js
# This is where you make a call to the 'get' method in the 'user.factory.js'.
# And you gave to inject 'userSvc' in this file so as to connect to the 'user.factory.js' file.
# 'app.foo.admin' refers to your directory structure i.e. app/foo/admin/user-list.controller.js
(function() {
'use strict';
angular
.module('app.foo.admin')
.controller('UsersListController', UsersListController);
/* #ngInject */
function UsersListController(
$scope,
$state,
$timeout,
$log,
userSvc) {
var vm = this;
vm.loading = false;
vm.userSvc = userSvc;
activate();
function activate() {
// init users
vm.userSvc.get().then(
function(users) {
initSearchString(users);
vm.users = users;
},
function(error) {
$log.error(error);
}
);
}
}
})();

Is it a good idea to communicate between services?

I was thinking of using one service to store variables and call another service which stores the functions that call the web API that would be called by service1 - would this be a bad idea?
For example, I have a controller that only gets the values from Service1:
(function() {
'use strict';
angular
.module('app.event')
.controller('Controller1', Controller1);
Controller1.$inject = ['service1', '$stateParams'];
/**
* Controller 1
* #constructor
*/
function Controller1(service1, $stateParams) {
// Declare self and variables
var vm = this;
vm.number = 0;
init();
/**
* Initializes the controller
*/
function init() {
service1.refreshCount($stateParams.id);
vm.number = service1.getCount();
}
}
})();
Below is Service 1, which only stores variables and a reference to a function which is in Service2:
(function() {
'use strict';
angular
.module('app.event')
.factory('service1', service1);
service1.$inject = ['service2'];
/**
* The event index service
* #constructor
*/
function service1(service2) {
// Declare
var count = 0;
// Create the service object with functions in it
var service = {
getCount: getCount,
setCount: setCount,
refreshCount: refreshCount
};
return service;
///////////////
// Functions //
///////////////
/**
* Refreshes the count value
*/
function refreshCount(id) {
service2.getCounts(id).then(
function (response) {
setCount(response.data.Count);
});
}
/**
* Returns the count value
*/
function getCount() {
return count;
}
/**
* Updates the count
* #param {int} newCount - The new count value
*/
function setCount(newCount) {
count = newCount;
}
}
})();
And below would be a part of Service2, where the function is called from Service1:
// Gets a count from the DB
getCounts: function(id) {
$http({ url: APP_URLS.api + 'WebApiMethodName/' + id, method: 'GET' }).then(function (response) {
return response.data.Count;
});
},
etc : function() {},
etc : function() {}
Is this a good idea, or is there something bad about communicating between services in this way?

Make a factory not Singleton in AngularJS

UPDATE:
Thanks for your reply!
I've rewritten my code:
(function () {
'use strict';
angular
.module('Services', []).factory('services', ['$http', function($http,services) {
function services($http) {
var serviceProvider = function () {
this.data = [];
this.errors = [];
}
var model = {
getInstance:function(){ return new serviceProvider(); }
}
serviceProvider.prototype.init = function(){//put some init stuff here
}
serviceProvider.prototype.getFromRESTServer = function(msg,callback){
return $http.jsonp("http://xxxxxxx/JSONEngine.php?callback=JSON_CALLBACK&action="+callback+"&"+msg);
}
return model;
}
}])
})();
And my controller is defined as:
var uniqueModelInstance = services.getInstance();
uniqueModelInstance.init();
uniqueModelInstance.getFromRESTServer("username="+$scope.username+"&password="+$scope.password,"register").success(function (data) {...}
Are they correct? Now I obtain "Cannot read property 'getInstance' of undefined".
Any suggestion?
Thanks in advance.
Giuseppe
I have an angular factory defined in this way:
services.factory('services', ['$http', function($http) {
var service = {};
return {
getFromRESTServer: function (msg,callback){
return $http.jsonp("http://myUrl/JSONEngine.php?callback=JSON_CALLBACK&action="+callback+"&"+msg);
}
}
}]);
and a controller with doLogin function:
home.controller('registrazioneTraduttoreCtrl', ['$scope', '$rootScope', '$window', 'services', '$location', 'customFactory',
function ($scope, $rootScope, $window, services, $location, customFactory) {
$scope.doLogin= function(username, password) {
services.getFromRESTServer("username="+username+"&password="+password,"login").
success(function (data) {
if(data.jsonError != null || data.errCode != null)
{
alert (data.errMsg);
}
else {
// DO STUFF...
}).error(function(data, status) {
console.error('Repos error', status, data);
})
.finally(function() {
console.log("finally finished repos");
});
}
}]);
The getFromRESTServer can be also executed by another function in another controller (there are 2 different Registration form in my html page and then they call doLogin function).
When I debug my application, the debugger skip from:
services.getFromRESTServer("username="+username+"&password="+password,"login") line (in doLogin function) to the end of getFromRESTServer funcion without going in and then re-execute the doLogin function with username and password NULL and now it enter in the core of getFromRESTServer function.
Any ideas?
Thanks in advance.
Giuseppe
You can do this by returning a new instance of any factory which is called. Look at this Plunker or try the following codes:
/**
* Non singleton factory example
*
* #name non-singleton-example
* #author Nils Gajsek <info#linslin.org>
*/
(function () {
//use strict -> ECMAScript5 error reporting
'use strict';
// ################################################ angularJS Module define // ####################################
/**
* DB service, part of app module
*/
angular
.module('app.model', []) // [-_-]
.factory('model', ['$http', model]);
/**
* Model factory wrapper
*
* #param {object} $http
*
* #returns self
*/
function model($http) {
// ################################################## Vars // ##############################################
var serviceProvider = function(){
/**
* Data store
* #type {Array}
*/
this.data = [];
/**
* Error store
* #type {Array}
*/
this.errors = [];
}
// ############################################### Functions // ############################################
/**
* Model instance provider handler
* This object is returned on the end of this object
*/
var model = {
getInstance:function(){ return new serviceProvider(); }
}
/**
* Model init function, provides
*/
serviceProvider.prototype.init = function(){
//put some init stuff here
}
/**
* Example function
*
* #returns {{this}}
*/
serviceProvider.prototype.someFunction = function(){
//do some stuff with model
}
//return model -> non-singleton model instance object
return model;
}
})();
This is how you receive it as unique instance.
var uniqueModelInstance = model.getInstance();
uniqueModelInstance.init();
Or better (but you need to return the instance itself by calling init() the function)
var uniqueModelInstance = model.getInstance().init();

Opening/Closing the modal window triggers window resize

In my angular app, i use modal windows for filters/selectors and there are many components which are binded with window resize event like SVG, wordcloud etc.. Whenever i open or close the modal window, it triggers window resize event which results in reloading of all those components (Charts)..
Is there a way to stop the triggering of window resize event while opening or closing the modal window..?.. I googled and got a thread about this issue which is still open..
https://github.com/driftyco/ionic/issues/2309
The window.resize is triggered intentionally by the framework itself -- from the source: ionic.trigger('resize');.
solution
option 1
You can just comment out the above mentioned line and create your own bundle of the ionic framework.
option 2
You can use decorator to replace $ionicModal service altogether by copying and modifying the source code. Let me know if anything unclear. I personally prefer this option as it should be easier to maintain in the long run and let you customise the behaviour of the modal even further.
HERE is an example of what I mean:
.config(function($provide){
$provide.decorator('$ionicModal',function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log) {
var PLATFORM_BACK_BUTTON_PRIORITY_MODAL = 200;
var delegate = (function(){
/**
* #ngdoc controller
* #name ionicModal
* #module ionic
* #description
* Instantiated by the {#link ionic.service:$ionicModal} service.
*
* Be sure to call [remove()](#remove) when you are done with each modal
* to clean it up and avoid memory leaks.
*
* Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
* scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
* called when the modal is removed.
*/
var ModalView = ionic.views.Modal.inherit({
/**
* #ngdoc method
* #name ionicModal#initialize
* #description Creates a new modal controller instance.
* #param {object} options An options object with the following properties:
* - `{object=}` `scope` The scope to be a child of.
* Default: creates a child of $rootScope.
* - `{string=}` `animation` The animation to show & hide with.
* Default: 'slide-in-up'
* - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
* the modal when shown. Default: false.
* - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
* Default: true.
* - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
* back button on Android and similar devices. Default: true.
*/
initialize: function(opts) {
ionic.views.Modal.prototype.initialize.call(this, opts);
this.animation = opts.animation || 'slide-in-up';
},
/**
* #ngdoc method
* #name ionicModal#show
* #description Show this modal instance.
* #returns {promise} A promise which is resolved when the modal is finished animating in.
*/
show: function(target) {
var self = this;
if (self.scope.$$destroyed) {
$log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');
return;
}
var modalEl = angular.element(self.modalEl);
self.el.classList.remove('hide');
$timeout(function() {
$ionicBody.addClass(self.viewType + '-open');
}, 400);
if (!self.el.parentElement) {
modalEl.addClass(self.animation);
$ionicBody.append(self.el);
}
if (target && self.positionView) {
self.positionView(target, modalEl);
// set up a listener for in case the window size changes
ionic.on('resize',function() {
ionic.off('resize',null,window);
self.positionView(target,modalEl);
},window);
}
modalEl.addClass('ng-enter active')
.removeClass('ng-leave ng-leave-active');
self._isShown = true;
self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
self.hardwareBackButtonClose ? angular.bind(self, self.hide) : angular.noop,
PLATFORM_BACK_BUTTON_PRIORITY_MODAL
);
self._isOpenPromise = $q.defer();
ionic.views.Modal.prototype.show.call(self);
$timeout(function() {
modalEl.addClass('ng-enter-active');
// ionic.trigger('resize');
self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
self.el.classList.add('active');
self.scope.$broadcast('$ionicHeader.align');
}, 20);
return $timeout(function() {
//After animating in, allow hide on backdrop click
self.$el.on('click', function(e) {
if (self.backdropClickToClose && e.target === self.el) {
self.hide();
}
});
}, 400);
},
/**
* #ngdoc method
* #name ionicModal#hide
* #description Hide this modal instance.
* #returns {promise} A promise which is resolved when the modal is finished animating out.
*/
hide: function() {
var self = this;
var modalEl = angular.element(self.modalEl);
self.el.classList.remove('active');
modalEl.addClass('ng-leave');
$timeout(function() {
modalEl.addClass('ng-leave-active')
.removeClass('ng-enter ng-enter-active active');
}, 20);
self.$el.off('click');
self._isShown = false;
self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
self._deregisterBackButton && self._deregisterBackButton();
ionic.views.Modal.prototype.hide.call(self);
// clean up event listeners
if (self.positionView) {
ionic.off('resize',null,window);
}
return $timeout(function() {
$ionicBody.removeClass(self.viewType + '-open');
self.el.classList.add('hide');
}, self.hideDelay || 320);
},
/**
* #ngdoc method
* #name ionicModal#remove
* #description Remove this modal instance from the DOM and clean up.
* #returns {promise} A promise which is resolved when the modal is finished animating out.
*/
remove: function() {
var self = this;
self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
return self.hide().then(function() {
self.scope.$destroy();
self.$el.remove();
});
},
/**
* #ngdoc method
* #name ionicModal#isShown
* #returns boolean Whether this modal is currently shown.
*/
isShown: function() {
return !!this._isShown;
}
});
var createModal = function(templateString, options) {
// Create a new scope for the modal
var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
options.viewType = options.viewType || 'modal';
angular.extend(scope, {
$hasHeader: false,
$hasSubheader: false,
$hasFooter: false,
$hasSubfooter: false,
$hasTabs: false,
$hasTabsTop: false
});
// Compile the template
var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
options.$el = element;
options.el = element[0];
options.modalEl = options.el.querySelector('.' + options.viewType);
var modal = new ModalView(options);
modal.scope = scope;
// If this wasn't a defined scope, we can assign the viewType to the isolated scope
// we created
if (!options.scope) {
scope[ options.viewType ] = modal;
}
return modal;
};
return {
/**
* #ngdoc method
* #name $ionicModal#fromTemplate
* #param {string} templateString The template string to use as the modal's
* content.
* #param {object} options Options to be passed {#link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
* #returns {object} An instance of an {#link ionic.controller:ionicModal}
* controller.
*/
fromTemplate: function(templateString, options) {
var modal = createModal(templateString, options || {});
return modal;
},
/**
* #ngdoc method
* #name $ionicModal#fromTemplateUrl
* #param {string} templateUrl The url to load the template from.
* #param {object} options Options to be passed {#link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
* options object.
* #returns {promise} A promise that will be resolved with an instance of
* an {#link ionic.controller:ionicModal} controller.
*/
fromTemplateUrl: function(url, options, _) {
var cb;
//Deprecated: allow a callback as second parameter. Now we return a promise.
if (angular.isFunction(options)) {
cb = options;
options = _;
}
return $ionicTemplateLoader.load(url).then(function(templateString) {
var modal = createModal(templateString, options || {});
cb && cb(modal);
return modal;
});
}
}
})();
console.log(delegate);
return delegate;
});
})
caution
Changing the behaviour may have some unwanted side effects, but I do not know ionic framework that much. Moreover, if there are any negative side effects then surely that is a bad design of the framework itself, as general events should not be used for framework specific purposes. If I had a bit more time I will try to make a PR to have triggering of the event optional.

reading the firebug console in javascript

I'm looking for a way to read the most recent command that was logged to the firebug console.
For example, I could have something that does
console.debug('The most current request URI is /sweatsocks');
And then another piece of (pseudo)code could then
if (mostRecentConsoleEntry().endsWith('/sweatsocks')) {
// do some stuff
}
The context being the debug statement would be in the code under test, and the console checking would be done inside a selenium script. This would let me observe information buried deep in js functions as well as stuff that is built at runtime.
You could overwrite the console.log function to add whatever extra functionality you need.
var oldLog = console.log;
var lastLog;
console.log = function () {
// do whatever you need to do here: store the logs into a different variable, etc
// eg:
lastLog = arguments;
// then call the regular log command
oldLog.apply(console, arguments);
};
This won't be the most bulletproof solution, since console allows printf style syntax:
console.log("%d + %d = %s", 1, 3, "four");
...but it's probably a start for you.
Don't try and override console.debug, implement a function that does console.debug plus what you need.
var debugCalls = [ ];
function myDebug(errorMessage){
console.debug(errorMessage); //maintain original functionality
debugCalls[debugCalls.length] = errorMessage;
//the previous argument to myDebug is debugCalls[debugCalls.length]
//you may also want to call an ajax function to report this error
mailError(errorMessage);
}
Could you rewrite the console.log(), and append all logs to an array? Then fire up the original console.log() and repeat what it's doing to get your debug output on the console?
Here's a more elaborate version I put together:
/**
* Console log with memory
*
* Example:
*
* console.log(1);
* console.history[0]; // [1]
*
* console.log(123, 456);
* console.history.slice(-1)[0]; // [123, 456]
*
* console.log('third');
* // Setting the limit immediately trims the array,
* // just like .length (but removes from start instead of end).
* console.history.limit = 2;
* console.history[0]; // [123, 456], the [1] has been removed
*
* #author Timo Tijhof, 2012
*/
console.log = (function () {
var log = console.log,
limit = 10,
history = [],
slice = history.slice;
function update() {
if (history.length > limit) {
// Trim the array leaving only the last N entries
console.history.splice(0, console.history.length - limit);
}
}
if (console.history !== undefined) {
return log;
}
Object.defineProperty(history, 'limit', {
get: function () { return limit; },
set: function (val) {
limit = val;
update();
}
});
console.history = history;
return function () {
history.push(slice.call(arguments));
update();
return log.apply(console, arguments);
};
}());
You might wanna implement a queue. Expanding on Devin's answer: (something like this)
var window.log = [];
logger function(msg) {
var log_length = 10;
console.log(msg);
window.log.push(msg);
if(window.log.length > log_length) {
window.log.shift()
}
}
See:
How do you implement a Stack and a Queue in JavaScript?
http://aymanh.com/9-javascript-tips-you-may-not-know#string-concatenation-vs-arrayjoin

Categories