What's the right way to test this controller? - javascript

I'm writing a test for a controller, using angular.js + jasmine.
Let's say I have a controller:
angular.module('app').controller('HomePageCtrl', function($scope, Homepage, posts, topics, photos, sideBar) {
$scope.slide = (photos || [])[0];
$scope.posts = Homepage.posts(posts);
$scope.topics = Homepage.topics(topics);
$scope.sideBar = sideBar;
$scope.template = '/templates/home-page.html';
});
This controller has only scope variables assignments not functions. How can I write tests for this controller? I need to check if each variables is defined? Each variable has the right value?

You could use the log service (if that is what you mean) to test if your variables are not empty
angular.module('logExample', [])
.controller('LogController', ['$scope', '$log', function($scope, $log) {
$scope.$log = $log;
$scope.message = 'Hello World!';
}]);

You can put all initialization logic into an exposed function, to be able to call it from tests.
angular.module('app').controller('HomePageCtrl', function($scope, Homepage, posts, topics, photos, sideBar) {
$scope.activate = function () {
$scope.slide = (photos || [])[0];
$scope.posts = Homepage.posts(posts);
$scope.topics = Homepage.topics(topics);
$scope.sideBar = sideBar;
$scope.template = '/templates/home-page.html';
}
$scope.activate();
});

Related

testing angularjs 1 factory method is automatically called inside a controller with jasmine

I'm using ruby on rails with angularjs one, and testing it with teaspoon-jasmine for the first time and am running into issues. Basically, I have a controller that creates an empty array and upon load calls a factory method to populate that array. The Factory makes an http request and returns the data. Right now, i'm trying to test the controller, and i'm trying to test that 1) the factory method is called upon loading the controller, and 2) that the controller correctly assigns the returned data through it's callback. For a while I was having trouble getting a mocked factory to pass a test, but once I did, I realized I wasn't actually testing my controller anymore, but the code below passes. Any tips on how I can still get it to pass with mock, promises/callbacks, but accurately test my controller functionality. Or should I even test the this at all in my controller since it calls a factory method and just gives it a callback? My 3 files are below. Can anyone help here? It would be greatly appreciated
mainController.js
'use strict';
myApp.controller('mainController', [ 'mainFactory', '$scope', '$resource', function(factory, scope, resource){
//hits the /games server route upon page load via the factory to grab the list of video games
scope.games = [];
factory.populateTable(function(data){
scope.games = data;
});
}]);
mainFactory.js
'use strict';
myApp.factory('mainFactory', ['$http', '$routeParams', '$location', function(http, routeParams, location) {
var factory = {};
factory.populateTable = function(callback) {
http.get('/games')
.then(function(response){
callback(response.data);
})
};
return factory;
}]);
And finally my mainController_spec.js file
'use strict';
describe("mainController", function() {
var scope,
ctrl,
deferred,
mainFactoryMock;
var gamesArray = [
{name: 'Mario World', manufacturer: 'Nintendo'},
{name: 'Sonic', manufacturer: 'Sega'}
];
var ngInject = angular.mock.inject;
var ngModule = angular.mock.module;
var setupController = function() {
ngInject( function($rootScope, $controller, $q) {
deferred = $q.defer();
deferred.resolve(gamesArray);
mainFactoryMock = {
populateTable: function() {}
};
spyOn(mainFactoryMock, 'populateTable').and.returnValue(deferred.promise);
scope = $rootScope.$new();
ctrl = $controller('mainController', {
mainFactory: mainFactoryMock,
$scope: scope
});
})
}
beforeEach(ngModule("angularApp"));
beforeEach(function(){
setupController();
});
it('should start with an empty games array and populate the array upon load via a factory method', function(){
expect(scope.games).toEqual([])
mainFactoryMock.populateTable();
expect(mainFactoryMock.populateTable).toHaveBeenCalled();
mainFactoryMock.populateTable().then(function(d) {
scope.games = d;
});
scope.$apply(); // resolve promise
expect(scope.games).toEqual(gamesArray)
})
});
Your code looks "non-standard" e.g still using scope.
If you are just starting with angular I hardly recommend you to read and follow this:
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
Angular controllers cannot be tested, extract the logic into factories/services and test from there.

Angular Unit Testing Controllers

My controller doesnt do a lot other than call methods in a service, the service wraps up and returns its functions, I have already written unit tests for the service mocking the http request.
Is it even worth unit testing the controller in this instance and if so what would I be testing as I have already tested the service functionality.
Below is my controller:
'use strict';
/* Controllers */
var calculatorControllers = angular.module('calculatorControllers', []);
calculatorControllers.controller('BodyController', ['$scope',
function($scope) {
$scope.toggleNavBarActive = function($event) {
$($event.currentTarget).parent().find('.active').removeClass('active');
$($event.currentTarget).addClass('active');
};
}]);
calculatorControllers.controller('CalculatorCtrl', ['$scope', 'CalculatorService',
function($scope, CalculatorService) {
$scope.orderProp = 'asc';
$scope.result = ' awaiting calculation';
$scope.sum = {};
$scope.add = function(val1, val2) {
var promise = CalculatorService.add(val1, val2);
promise.then(function(response) {
$scope.result = response;
});
};
}]);
calculatorControllers.controller('AboutCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
}]);
Is it even worth unit testing the controller in this instance
Yes, you should aim for 100% coverage, not matter controller or service. I would test two things here (Jasmine):
it('inits $scope', function() {
var $scope = {};
$controller('PasswordController', { $scope: $scope });
expect($scope.orderProp).toEqual('asc');
expect($scope.result).toEqual(' awaiting calculation');
expect($scope.sum).toEqual({});
});
it('calls CalculatorService and sets the result', function() {
var $scope = {};
$controller('PasswordController', { $scope: $scope });
$scope.sum(1, 2);
expect(CalculatorServiceMock).toHaveBeenCalledWith(1, 2);
resolveCalculatorServiceMockAddSpyWith(3);
expect($scope.result).toEqual(3);
});
The only case when the controller methods don't require testing is
$scope.calculator = CalculatorService;
So all view calls like {{ calculator.sum(...) }} are done by the service.
In every other case controller methods should be tested. Since CalculatorService unit was already tested, it has to be mocked in order for controller logic to be tested in isolation.

How to access $scope from one controller to another in angular

I have these controller codes from different js files.
NLGoalsCtrl.js
angular.module('mysite').controller('NLGoalsCtrl', function ($scope) {
$scope.goals_selected = [];
});
NLSessionsCtrl.js
angular.module('mysite').controller('NLSessionsCtrl', function ($scope) {
//access $scope.goals_selected here
});
I need to be able to access the $scope.goals_selected from the NLSessionsCtrl. How do I do this? Thanks guys.
Use a factory/service to store the goals which will be responsible for sharing data among the controllers.
myApp.factory('myService', [function() {
var goals = {};
return {
getGoals: function() {
return goals
},
setGoals: function(op) {
goals = op;
},
}
}])
.controller('NLGoalsCtrl', [function($scope, myService) {
$scope.goals_selected = {};
//Update goals_selected
myService.setGoals($scope.goals_selected );
}])
.controller('NLSessionsCtrl', [function($scope, myService) {
//Fetch
$scope.goals_selected = myService.getGoals();
}]);
$scope is an "object" that "binds" to DOM element where you apply controller. So the context of $scope remains inside the controller.
If you want to access the variables in 2 controllers , try to share it via a service/Factory or $rootScope
Here is a sample App
the one that you want to access in any controller assign it to the $rootScope.
and access it in any controller you want.

AngularJS: Unit Testing Directive w/ Promise returned from a Service

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...)
})
})

How do I pass value to different controller in Angular js

I am using angular framework and trying to pass $scope to different controller in my app.
UPDATE:
My problem is I wan't obtain the $scope data until user click a button.
.controller('firstCtrl', function($scope) {
$scope.getTest = function(){
$scope.test1 = 'good.'
$scope.test2 = 'bad.'
…..more
}
})
I need to pass $scope object to different controller
.controller('secondCtrl', function($scope) {
console.log($scope.test1)
})
How do I do it? Thanks!
In order to share data between two controllers you need to use factory.
For more details, please watch this video: "AngularJS Video Tutorial: Sharing Data Between Controllers" by John Lindquist.
To share information between controllers, you use services. A service can be created like this:
//Create angular main app module
var app = angular.module('myApp', []);
//create a service
app.service('sharedService', function () {
this.test1 = [1, 2, 3, 4];
});
//one controller, injecting the service
app.controller('firstCtrl', function ($scope, sharedService) {
sharedService.test1[0] = 5;
console.log(sharedService.test1[0]) //5, 2, 3, 1
});
//two controller, injecting the same service
app.controller('secondCtrl', function ($scope, sharedService) {
sharedService.test[1] = 4;
console.log(sharedService.test1[0]) //5, 4, 3, 1
});
Here is an example I just created on jsFiddle:
http://jsfiddle.net/NHtFu/
Use custom events on the $rootScope
.controller('firstCtrl',['$rootScope', function($rootScope) {
$scope.getTest = function(){
$rootScope.your_object = {foo:bar}
$rootScope.$emit('custom_event');
}
}])
.controller('secondCtrl', function($scope,$rootScope) {
$rootScope.$on('custom_event',function(){
//do stuff
//$rootScope.your_object is available
})
});
You may need to unbind the root scope from that event if the controllers instantiate more then once
There may be an objection against 'polluting' the root scope but that's what its there for.

Categories

Resources