AngularJs - How to write testable controllers with private Methods - javascript

I'm trying to write a test for one of my controller, using angular.js + jasmine.
Let's say I have a controller
angular.module('app').controller('MyCtrl', function() {
this.myFunc = function() {
// ...
};
activate();
function activate() {
this.myFunc();
}
});
This controller have a function called activate() that is called when the controller is created.
How can I write a test for the activate() function? (like this: when the controller is created, should call a controller function "myFunc()")
I tried to write something like this:
describe('activate() controller', function() {
it('should call function myFunc', inject(function($rootScope, $controller) {
var locals = {$scope: $rootScope.$new()};
var controller = $controller('MyCtrl', locals);
spyOn(controller, 'myFunc').toHaveBeenCalled();
});
}
But I get the error:
Expected spy myFunc to have been called.
I think at the point I create my spy, the controller already called the activate function.
Is there a way to test a controller like this?

The code example you have above executes the myFunc method upon initialization. Therefore, by the time you attach the spy, it has already been executed. The better way of testing would be to inspect what transformations the myFunc has performed.
If the method were part of a service, you could setup your spy in your inject, and then initialize the controller and expect the service method to have been called.

Related

Stop event listeners on '$destroy'. TypeError: $on is not a function;

I'm trying to stop all event listeners while scope is destroyed.
I get this error:
TypeError: vm.$on is not a function;
Neither vm.on(..) works
angular.module('app.layout')
.controller('DashboardController', DashboardController);
DashboardController.$inject = ['$interval','dataservice'];
function DashboardController($interval, dataservice) {
var vm = this;
vm.name = "DashboardController";
console.log('Init Dashboard Controller');
initEvents();
/*
...
*/
/////////////////////////////
function initEvents() {
vm.$on('$destroy', function() {
vm.stop();
console.log('DashboardController scope destroyed.');
})
}
The problem is that vm doesn't have the $on(... declared, you must use $scope instead. Inject it on your controller and declare like $scope.$on.
When using controllerAs syntax, this very often missunderstood that you shouldn't use $scope at all. However, the recomendation is to avoid using $scope for certain activities not abolish it from your controller. Scope always will exists, it's an internal of your controller, just don't use it like a view model, but you can use it anyways for such tasks like, listen to events, broadcast, emmit, etc.
Try something like (after you've injected $scope on your dependencies):
$scope.$on('$destroy', function() {
vm.stop();
console.log('DashboardController scope destroyed.');
})

CallThrough injected spy

I'm doing some unitTests and my scenario is the following. I have like 50 tests whose call to a service function must be the same, but for one single test It will be so helpfull if I can call the original method. I tried with the and.callThrough but It's not working correctly. I'm trying to override the spy too but I can't. What I'm doing wrong?
beforeEach(inject(function($controller, _myService_){
spyOn(_myService_, 'getSomeData').and.callFake(function(data, params){
return dummyData;
});
createController = function() {
return $controller('MyCtrl',{
$uibModalInstance: modalInstance,
myService: _myService_,
injectedData: injectedData
});
};
}));
This is my test case.
it('My test case', function(){
controller = createController();
controller.myService.getSomeData = jasmine.createSpy().and.callThrough()
});
I'm using jasmine 2.0 and that test case is continuously calling the callFake function.
thanks
jasmine.createSpy().and.callThrough() is unaware of the spied method and there's no way how it can know about it, calling it just results in calling a noop function.
Spying strategy can be changed for existing spies,
controller.myService.getSomeData.and.callThrough();

Testing a function in factory

.factory('Tag', function($window) {
var Context = {};
function reset() {
return Context !== {} ? Context : {};
}
return{
reset:reset
};
})
I have done testing like this
describe('method: reset()', function(){
it('should reset the Context variable', function(){
spyOn(Tag, 'reset').andCallFake(function(){
return Context;
});
expect(Context).toEqual({});
});
afterEach(function(){
if(Context!== {}){
Context = {};
}
})
});
Is, this test is accurate, if yes then why my test coverage is not increasing..
you are calling a fake function not the real one, so the code in your actual function is never executed.
your code coverage tool only marks code that was actually hit.
andCallFake is used to mock an external function which you are not interested in testing and you just want some mock response when the code you are actually testing calls it.
your code should make a real call to ...
Tag.reset()
now if Tag.reset() makes a call to code in another service which you do not want to test then you can use callFake on that call.
Remember this is "unit" testing. The "unit" of code you want to test is the code inside your service, not outside of your service.

Angular constructor in a controller

I tried this with no luck:
app.controller('PageLayoutController', function ($scope)
{
// Scope properties
$scope.PageMap = {};
// Constructor
function PageLayoutController() {
alert( "contructing" );
}
return PageLayoutController;
});
I'm looking for a default or popular way of defining the construct of these controllers in angular.
I'm aware i can just create a function called Construct and then call it first, but i wondered if there was an official way of doing it?
I assume you may want to invoke the defined function during Controller getting instantiated. If that's the requirement, you can follow the following syntax.
angular.module('myApp', [])
.controller('PageLayoutController', '$scope', function($scope) {
// Scope properties
$scope.PageMap = {};
$scope.PageLayoutController = function() {
// Do stuffs here
};
// Call the function when the Controller get first invoked
$scope.PageLayoutController();
});
Also you can listen into the $routeChangeStart event and call the function as well.
$scope.$on('$routeChangeStart', function(next, current) {
... you could trigger something here ...
});
Also you can use any of the following events as well.
$routeChangeSuccess
$routeChangeError
The controller function is already a constructor so anything you write in the its body will be executed on construction.
Not quite sure what you are exactly trying to do. But being specific, angular instantiates the controller constructor with new operator when it is needed. So what you are trying to do is more of javascript specific question than angular specific. It does it as:
new ctor(); //or new ctor;
had it been new (ctor()) your code will work, but that is not how it happens obviously. The function reference passed in is newed up, not the result of the function execution. So in your case if you were to do this then you need to return the newed up instance. i.e
return new PageLayoutController;
Example

Accessing instance functions inside controller from function on $scope

I have a controller that looks like this:
function controller($scope)
{
this.helper() = function()
{
// some processing
};
$scope.doSomething = function()
{
helper();
};
}
When doSomething is called, I get an error saying that helper() is not defined. Putting 'this' in front of the call to helper() doesn't work either as 'this' here refers to $scope and not to the controller instance.
My question is: is there a way to call such local helper functions from within a function on the scope? (I know I could just put helper() on the $scope too but would rather not as it's strictly a convenience function, not something to be called from the view.)
The reason for structuring the code like this is to facilitate separate testing of the helper() function from a unit test.
function controller($scope)
{
this.helper() = function()
{
// some processing
};
var helperScope = this;
$scope.doSomething = function()
{
helperScope.helper();
};
}
Of course, give it a more descriptive name than that, but the basic idea is to assign this to a variable within the scope you want.

Categories

Resources