Using spies in non testing code in angularjs - javascript

I recently dug a little deeper into unit testing. I was wondering if there is a way to use spies in production code as well. I've a tracking service. It would be nice to access other services and maybe even controllers, without haveing to alter their code.
Is there a way to spy on methods being called from services and controllers in the application code and what would be the best way to do so?
EDIT
Atm. I'm using this pattern for spying on services:
var vSetFNTrigger = function (sEvent, fnTrigger) {
fnTrigger.obj[fnTrigger.sMethod] = (function () {
var fnCached = fnTrigger.obj[fnTrigger.sMethod];
return function () {
$rootScope.$broadcast(sEvent, {});
return fnCached.apply(this, arguments);
};
})();
};
fnTrigger: {
obj: formData, // the service
sMethod: 'qPost' // the method to spy on
},
EDIT 2
I forgot to add a return to the inner function.

There should be nothing stopping you from doing this, although I think it is the wrong tool for the job.
If you are in Angular, you should consider using a decorator pattern. You can even use the provider decorator to intercept pretty much anything in Angular.
For instance, you might have a spy function that looks like this:
function createSpy(serviceName, source, spyNames, rootScope) {
var spy = angular.extend(angular.isFunction(source) ? function () {
console.log("Called " + serviceName + '()', arguments);
// broadcast with rootScope
return source.apply(source, arguments);
} : {}, source);
spyNames.forEach(function(name) {
var original = spy[name];
spy[name] = function() {
console.log("Called " + serviceName + '.' + name, arguments);
// broadcast with rootScope
return original.apply(spy, arguments);
};
});
return spy;
}
Then, you can create a generic function to generate a decorator:
function decorateWithSpy($provide, service, spyNames) {
$provide.decorator(service, function($delegate, $rootScope) {
return createSpy(service, $delegate, spyNames, $rootScope);
});
}
You can configure your spies like this:
app.config(function($provide) {
decorateWithSpy($provide, '$http', ['get']);
decorateWithSpy($provide, '$compile', []);
});
Doing this causes all of my $http and $compile functions to get printed to the console.

Related

How to DRY up $scope-manipulating code across controllers in angularJS

Pardon if this question is a total blow-off... Just getting warmed-up into the world angularJS.
I have these two controllers: seekerController and wizardController...
Inside the wizardController, I have a chat Scope object, and I have implemented a bunch of functions that are manipulating this chat Scope object.
Going back to the other controller now, ( seekerController ), I discover that I need to have basically a direct replica of this chat Scope object and all the other functions manipulating it as I have inside wizardController
The obvious way is just to copy all these into my other controller, and my work is done under a minute, but then I'll have a lot of repeated stuffs everywhere...
So: I'm looking for a way where I can have this(the code) in a single place, but still be able to have access to this chat Scope object from both controllers, as well as all the other functions working seamlessly.
Update - add code samples:
//seekerController
angular.module('cg.seeker', [])
.controller('SeekerController', ['$scope', 'seekerService', 'timeService', 'chatService', '$stateParams', 'toastr',
function ($scope, seekerService, timeService, chatService, $stateParams, toastr) {
...
// THE CHAT BUSINESS
$scope.chat = { close: true };
chatService.unreadCount(function(count){
$scope.chat.unreadCount = count;
$scope.$apply();
});
chatService.listDialogs( function (dialogList) {
$scope.chat.dialogList = dialogList.items;
$scope.$apply();
} );
$scope.endChat = function () {
$scope.chat.close = true;
}
$scope.chatBox = function (dialogId, occupants_ids) {
$scope.chat.opponentId = getOpponentId(occupants_ids);
chatService.getMessages( dialogId, function (messageList) {
$scope.chat.messages = messageList.items;
$scope.chat.close = false;
$scope.$apply();
});
}
var getOpponentId = function (opponentId) {
if(typeof(opponentId) != 'object') {
return opponentId;
} else {
return opponentId.filter(function(x) { return x != $scope.seeker.chat_user.chat_id_string; })[0];
}
}
$scope.sendMsg = function (opponentId) {
var msg = {
type: 'chat',
body: $scope.chat.msg,
extension: {
save_to_history: 1,
}
};
chatService.sendMsg(opponentId, msg);
$scope.chat.msg = '';
}
...
I now have an exact replica of the above code in a second controller WizardController. Exactly same, with no changes... and even a third controller have some of these, though not all.
The next level of abstraction to angularjs controllers are
Factory
Service
Provider
You could use a service called maybe chatService which could contain the common code. You can inject the service into any controller which needs the common functionality and invoke the methods on the Service.
Do note that you could use any of the above three options even though I have mentioned just Service in the above statement.
EDIT 1:
You could move the common parts of the code from Controller to Service.
For example:- You could move the construction of msg object from controller to chatService. You controller would be simply -
$scope.sendMsg = function (opponentId) {
chatService.sendMsg(opponentId);
$scope.chat.msg = '';
}
And your chatService would be doing the hard-work.
$chatService.sendMsg = function (opponentId) {
var msg = {
type: 'chat',
body: $scope.chat.msg,
extension: {
save_to_history: 1,
}
};
sendMsg(opponentId, msg);
}
After simplifying the Controllers you could revisit to see if you could use only one controller instead of 3 as they seem to be doing similar function.

Is there any downside to using $injector.get() to get my dependencies in Angular?

My team wants to move to a style of dependency injection that is closer to the CommonJS/Node JS syntax for out Angular codebase:
var myDependency = require('myDependency');
I've started using $injector.get() directly inside at the top of my functions, so far with no obvious trouble. That means I've converted this:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['serviceA', 'serviceB', 'serviceC', function(serviceA, serviceB, serviceC) {
//My code here
}])
Into:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['$injector', function($injector) {
var serviceA = $injector.get('serviceA');
var serviceB = $injector.get('serviceB');
var serviceC = $injector.get('serviceC');
//My code here
}]);
Is there something I'm missing. Does not declaring the required dependencies outside of the function definition cause any sort of performance issues?
Note: this answer has not been tested.
After digging into the angular's code, I find this:
// in function createInjector
function provider(name, provider_) {
...
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
...
return providerCache[name + providerSuffix] = provider_;
}
...
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
...
// in function createInternalInjector
function invoke (...){
...
for (i = 0, length = $inject.length; i < length; i++) {
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
...
return fn.apply(self, args);
}
function instantiate(...){
...
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
...
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
Its calls seems like:
service invoked factory invoked instantiate
factory invoked provider invoked instantiate
So I think your questions is equal to Is calling $injector.get() one by one has the same performance as calling $injector.instantiate() once?
As the code shows, instantiate invoked invoke which actually invoked getService for each services injected by you. And $injector.get is just binding to getService.
So the answer to my equal question is True.
And the answer to your question is No, their performance is very close.
Please correct me if I'm wrong, thank you!
There are no significant difference between both pieces of code in the case of a service. $injector.get(...) call doesn't provide any overhead. But it provides a significant amount of extra characters per dependency.
There is a difference when the same thing is done with injectables that have local dependencies - controllers and route/state resolvers.
When these dependencies
app.controller('SomeCtrl', function ($scope, service) { ... });
are replaced with $injector.get(...), it will choke on $scope - it is local dependency. And with other dependencies being retrieved like that, controller loses its testability. The dependencies cannot be mocked with
$controller('SomeCtrl', { service: mockedService });
I personally don't see how $injector.get(...) may benefit the style of the project (and haven't seen a good style guide that would suggest it).
Node uses require function because it works for it, not because it is better or cooler. The alternative would be to pack each Node script into into AMD-like wrappers, which would be painful. Fortunately, we already have wrappers around Angular units!

How do you mock an angularjs $resource factory

I have a resource factory
angular.module('mean.clusters').factory('Clusters', ['$resource',
function($resource) {
return $resource('clusters/:clusterId/:action', {
clusterId: '#_id'
}, {
update: {method: 'PUT'},
status: {method: 'GET', params: {action:'status'}}
});
}]);
and a controller
angular.module('mean.clusters').controller('ClustersController', ['$scope',
'$location', 'Clusters',
function ($scope, $location, Clusters) {
$scope.create = function () {
var cluster = new Clusters();
cluster.$save(function (response) {
$location.path('clusters/' + response._id);
});
};
$scope.update = function () {
var cluster = $scope.cluster;
cluster.$update(function () {
$location.path('clusters/' + cluster._id);
});
};
$scope.find = function () {
Clusters.query(function (clusters) {
$scope.clusters = clusters;
});
};
}]);
I am writing my unit tests and every example I find is using some form of $httpBackend.expect to mock the response from the server, and I can do that just fine.
My problems is, when unit testing my controller functions I would like to mock the Clusters object. If I'm using $httpBackend.expect, and I introduce a bug in my factory every unit test in my controller will fail.
I would like to have my test of $scope.create test only $scope.create and not also my factory code.
I've tried adding a provider in the beforeEach(module('mean', function ($provide) { part of my tests but I cant seem to get it right.
I also tried
clusterSpy = function (properties){
for(var k in properties)
this[k]=properties[k];
};
clusterSpy.$save = jasmine.createSpy().and.callFake(function (cb) {
cb({_id: '1'});
});
and setting Clusters = clusterSpy; in the before(inject but in the create function, the spy gets lost with
Error: Expected a spy, but got Function.
I have been able to get a spy object to work for the cluster.$update type calls but then it fails at var cluster = new Clusters(); with a 'not a function' error.
I can create a function that works for var cluster = new Clusters(); but then fails for the cluster.$update type calls.
I'm probably mixing terms here but, is there a proper way to mock Clusters with spies on the functions or is there a good reason to just go with $httpBackend.expect?
Looks like I was close a few times but I think I have it figured out now.
The solution was the 'I also tried' part above but I was not returning the spy object from the function.
This works, it can be placed in either the beforeEach(module( or beforeEach(inject sections
Step 1: create the spy object with any functions you want to test and assign it to a variable that's accessible to your tests.
Step 2: make a function that returns the spy object.
Step 3: copy the properties of the spy object to the new function.
clusterSpy = jasmine.createSpyObj('Clusters', ['$save', 'update', 'status']);
clusterSpyFunc = function () {
return clusterSpy
};
for(var k in clusterSpy){
clusterSpyFunc[k]=clusterSpy[k];
}
Step 4: add it to the $controller in the beforeEach(inject section.
ClustersController = $controller('ClustersController', {
$scope: scope,
Clusters: clusterSpyFunc
});
inside your tests you can still add functionality to the methods using
clusterSpy.$save.and.callFake(function (cb) {
cb({_id: '1'});
});
then to check the spy values
expect(clusterSpy.$save).toHaveBeenCalled();
This solves both problems of new Clusters() and Clusters.query not being a function. And now I can unit test my controller with out a dependency on the resource factory.
Another way to mock the Clusters service is this:
describe('Cluster Controller', function() {
var location, scope, controller, MockClusters, passPromise, q;
var cluster = {_id : '1'};
beforeEach(function(){
// since we are outside of angular.js framework,
// we inject the angujar.js services that we need later on
inject(function($rootScope, $controller, $q) {
scope = $rootScope.$new();
controller = $controller;
q = $q;
});
// let's mock the location service
location = {path: jasmine.createSpy('path')};
// let's mock the Clusters service
var MockClusters = function(){};
// since MockClusters is a function object (not literal object)
// we'll need to use the "prototype" property
// for adding methods to the object
MockClusters.prototype.$save = function(success, error) {
var deferred = q.defer();
var promise = deferred.promise;
// since the Clusters controller expect the result to be
// sent back as a callback, we register the success and
// error callbacks with the promise
promise.then(success, error);
// conditionally resolve the promise so we can test
// both paths
if(passPromise){
deferred.resolve(cluster);
} else {
deferred.reject();
}
}
// import the module containing the Clusters controller
module('mean.clusters')
// create an instance of the controller we unit test
// using the services we mocked (except scope)
controller('ClustersController', {
$scope: scope,
$location: location,
Clusters: MockClusters
});
it('save completes successfully', function() {
passPromise = true;
scope.save();
// since MockClusters.$save contains a promise (e.g. an async call)
// we tell angular to process this async call before we can validate
// the response
scope.$apply();
// we can call "toHaveBeenCalledWith" since we mocked "location.path" as a spy
expect(location.path).toHaveBeenCalledWith('clusters/' + cluster._id););
});
it('save doesn''t complete successfully', function() {
passPromise = false;
scope.save();
// since MockClusters.$save contains a promise (e.g. an async call)
// we tell angular to process this async call before we can validate
// the response
scope.$apply();
expect(location.path).toHaveBeenCalledWith('/error'););
});
});
});

Best approach to implement safe $apply in Angular Service/Controller

I'm using the socket.ioclient in my Angular WebApp. I wrapped it up in a service, but that might not necessarily be of interest. However, since the incoming data is in a different scope than the controller which is using the service, I always have to call $scope.$apply. Even worse I found a few situations (during connect/reconnect) where I would have to use a safeApply as explained here.
I understand that this is an Angular Anti-Pattern, however I don't see a way around this.
Is there a general way to solve this (preferably inside the service) that does not pollute the controllers with a lot of $scope.$apply/safeApply?
BR,
Daniel
Here is also some code, working, but not nice:
angular.module('mean')
.controller('ConnectionStateController', function ($scope) {
$scope.safeApply = function (fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn && (typeof (fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
var socket = io.connect('http://localhost:3000');
$scope.message = 'Not connected';
socket.on('connect', function () {
$scope.safeApply(function () {
$scope.message = "Connected";
});
});
});
In your case, you know that socket.io operations are always going to be run asynchronously and that makes it a valid place to use $scope.$apply (towards the bottom of the page).
I would recommend not using safeApply and explicitly calling $scope.$apply. Using safeApply is going to cover up for genuine mistakes in implementation. Also, if you are wrapping your code in a service, you can inject the $rootScope in the service and call the async functions in $rootScope.$apply. Then you would not need to call $scope.$apply in each controller.
I'll answer the question myself, since I have a working solution using defers. Nevertheless I'm not sure if I did it right. Here's the service:
angular.module('mean')
.factory('ConnectionService', function ($q, $timeout) {
var bindings = {};
var socket = io.connect('http://localhost:3000');
// catch all events by overriding the socket's $emit
var $emit = socket.$emit;
socket.$emit = function (event, obj) {
console.log('***', 'on', '"' + event + '"', obj);
if (bindings.hasOwnProperty(event)) {
bindings[event].notify(obj);
}
$emit.apply(socket, arguments);
};
return {
socket: socket,
observe: function (event, callback) {
if (!bindings.hasOwnProperty(event)) {
bindings[event] = $q.defer();
}
bindings[event].promise.then(null, null, function (data) {
callback.apply(this, [ data ]);
});
}
};
});
This allows a controller to use the service like this:
ConnectionService.observe('news', function (data) {
$scope.message = data;
});
Not sure, if that's a nice solution or if it has side effects, that I don't see yet.
BR,
Daniel

Angularjs - what I'm doing wrong with injections?

In my previous question I got an answer hot to inject dependencies and while I tested it, everything worked well. Then I refactored the code and wanted to start the app implementation, but the injections stopped to work :(
http://jsbin.com/alemik/1/edit
In addittion to jsbin, here is the source:
var ABCS = angular.module("ABCS", []);
ABCS.Configuration = angular.module('ABCS.Configuration', []);
ABCS.Controllers = angular.module('ABCS.Controllers', []);
ABCS.Modules = angular.module("ABCS.Modules", []);
ABCS.Services = angular.module("ABCS.Services", []);
ABCS.run(["$rootScope", "$location", "ABCS.Services.LogService",
function ($rootScope, $location, logger) {
logger.log("app start");
}]);
ABCS.Configuration.factory("Environment", function () {
return {
logOutputElementId: "output",
hasConsole: console && console.log
}
});
//Log service
ABCS.Services.LogService = function (config) {
this.log = function (message) {
if (typeof (config.Environment.logOutputElementId) === "string") {
document.getElementById(config.Environment.logOutputElementId).innerHTML += message + "<br/>";
}
else if (config.Environment.hasConsole) {
console.log(message);
}
else {
alert(message);
}
};
};
ABCS.Services.LogService.$inject = ["ABCS.Configuration"];
ABCS.Services.factory("ABCS.Services.LogService", ABCS.Services.LogService);
What I miss? Why ABCS.Services.LogService can not be injected in current structure.
Thanks
When you've got several modules, you need to make sure that you declare your modules' dependencies. This tells the dependency injector to look in those modules when looking for providers.
var ABCS = angular.module("ABCS", [
'ABCS.Services', 'ABCS.Configuration', 'ABCS.Controllers',
'ABCS.Modules', 'ABCS.Services'
]);
I had to make a few more adjustments to get it working:
The dependency injector won't inject an entire module, and so ABCS.Services.LogService.$inject = ["ABCS.Configuration"]; wasn't working. I've changed that to ABCS.Services.LogService.$inject = ["ABCS.Configuration.Environment"]; and adjusted the associated factory in order to fit your naming conventions.
factory accepts a function, but won't call it as a constructor, and thus using this in your definition of LogService won't act as you are expecting it to. I've changed your factory function to define a constructor function, which is then instantiated and returned.
See a working version here: http://jsbin.com/alemik/2/edit
In my opinion , that's the angularJS way of doing things ( though i kept all the DI definitions ):
angular.module("ABCS", ['ABCS.Services', 'ABCS.Configuration', 'ABCS.Controllers', 'ABCS.Modules', 'ABCS.Services']);
angular.module('ABCS.Configuration', []);
angular.module('ABCS.Controllers', []);
angular.module("ABCS.Modules", []);
angular.module("ABCS.Services", []);
angular.module("ABCS").run(["$rootScope", "$location", "ABCS.Services.LogService",
function ($rootScope, $location, logger) {
logger.log("app start");
}]);
angular.module('ABCS.Configuration').factory("ABCS.Configuration.Environment", function () {
return {
logOutputElementId: "output",
hasConsole: console && console.log
};
});
angular.module("ABCS.Services").factory("ABCS.Services.LogService",["ABCS.Configuration.Environment",function (environment) {
function LogService() {
this.log = function (message) {
if (typeof (environment.logOutputElementId) === "string") {
document.getElementById(environment.logOutputElementId).innerHTML += message + "<br/>";
}
else if (environment.hasConsole) {
console.log(message);
}
else {
alert(message);
}
}
}
return new LogService();
}]);
angularJS allows to reopen a module definition in multiple files , and javascript namespacing is totally unecessary unless the object is not tight to the angularJS application.
Mixing javascript namespacing and DI namespacing makes code more error prone ,not more maintanable.

Categories