I have a controller with a local variable
function IndexCtrl($scope) {
var pagesById = [];
loadPages();
// snip
function loadPages() {
// pagesById gets populated
}
// snip
}
I'd like to test that pagesById is correctly populated but I'm not sure how to get at it from my it(). I don't need this variable to be in the $scope, it's just an intermediate set of information, so if I can avoid adding it to $scope that would be ideal.
it('scope.pages should populated based on pages data.', function() {
$httpBackend.flush();
expect(pagesById).toEqualData(mock_page_results);
});
gives me
ReferenceError: pagesById is not defined
Do I have any other options besides attaching it to $scope?
In your jasmine spec, first create the controller:
var ctrl;
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('myController', {
$scope: scope
});
}));
Then you can access its properties by doing ctrl.pagesById. Of course, rather than doing var pagesById you'll need to use this.pagesById in your controller.
Here is my way for testing local variables in Angular methods:
//------- Function for getting value of local variables
function getLocalVariable(sLocalVarName, obj, method, args){
method = method.toString();
let changedMethod = method.substring(0, method.lastIndexOf("}")) + ";" + "return " + sLocalVarName + "}";
eval(' changedMethod = ' + changedMethod);
return changedMethod.call(obj, args)
}
//----------- service
class assignStuffService {
getAvaliableStuff(shift) {
const params = {
agency_id: 0 ,
start_time: shift.data.shift.start_time,
end_time : shift.data.shift.end_time
};
}
}
//----------- part of spec
it('should set values to "params" object props', () => {
let shift = {
data:{
shift:{
start_time:'',
end_time:''
}
}
};
let params = getLocalVariable('params',
assignStuffService,
assignStuffService.getAvaliableStuff,
shift);
expect(params).toEqual({
agency_id: 0 ,
start_time: shift.data.shift.start_time,
end_time : shift.data.shift.end_time
});
});
Related
I've been searching a long time for an answer to the question I'm about to ask without success.
Let's say I have the following directive :
(function () {
'use strict';
angular
.module('com.acme.mymod')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
restrict: 'EA',
scope: {},
replace: true,
templateUrl: 'folder/myDirective/myDirective.tpl.html',
controller: "myDirectiveController",
controllerAs: 'vm',
bindToController: {
somedata: "#?",
endpoint: "#?"
},
link: link
};
return directive;
function link(scope, element) {
activate();
function activate() {
scope.$on('$destroy', destroy);
element.on('$destroy', destroy);
}
}
function destroy() {
}
}
})();
myDirectiveController is as follow:
(function () {
'use strict';
angular
.module('com.acme.mymod')
.controller('myDirectiveController', myDirectiveController);
myDirectiveController.$inject = ['utils', '$log'];
// utils is an external library factory in another module
function myDirectiveController(utils, $log) {
var vm = this;
vm.menuIsOpen = false;
function activate() {
vm.dataPromise = utils.getValuePromise(null, vm.somedata, vm.endpoint);
vm.dataPromise.then(function (result) {
vm.data = result.data;
}, function () {
$log.debug("data is not Valid");
});
}
activate();
}
})();
The spec file is as follow:
describe('myDirective Spec', function () {
'use strict';
angular.module('com.acme.mymod', []);
var compile, scope, directiveElem, utils;
beforeEach(function(){
module('com.acme.mymod');
inject(function($compile, $rootScope, $injector,utils){
compile = $compile;
scope = $rootScope.$new();
scope.$digest();
directiveElem = getCompiledElement();
//utils=utils;
console.log(utils.test());
});
});
function getCompiledElement(){
var element = angular.element('<div my-directive="" data-some-data=\' lorem\'></div>');
var compiledElement = compile(element)(scope);
scope.$digest();
return compiledElement;
}
it('should have a nav element of class', function () {
var navElement = directiveElem.find('nav');
expect(navElement.attr('class')).toBeDefined();
});
it('should have a valid json data-model' ,function () {
var data=directiveElem.attr('data-somedata');
var validJSON=false;
try{
validJSON=JSON.parse(dataNav);
}
catch(e){
}
expect(validJSON).toBeTruthy();
});
});
What I can't quite figure out is that every test I try to run, the directive is not compiled or created correctly I'm not sure.
I get :
Error: [$injector:unpr] Unknown provider: utilsProvider <- utils
I tried:
Injecting the controller or utils with $injector.get()
Looked at this post $injector:unpr Unknown provider error when testing with Jasmine
Any tips, pointers or clues as to what I'm doing wrong would be more than welcome
I found the solution after feeling a hitch in my brain :)
In the beforeEach function, all I needed to do is to reference my utils module name this way:
module('com.acme.myutilsmod');
This line "expose" modules components so consumers can use it.
How can I unit test a function that uses window.localStorage. Here are the two functions for checking and creating localStorage variable:
// checks if Initial localStorage variable is true
// returns boolean
var isInitial = function() {
var doc = window.localStorage.getItem("Initial");
console.log("intial: " + doc);
if(doc !== "true"){
return true;
}
else{
return false;
}
};
// create InitialSync localStorage item and sets it to true
// void
var createInitial = function() {
console.log("creating Initial");
window.localStorage.setItem("Initial","true")
};
Here is how I unit tested this:
describe('isInitial function', function() {
// clear localStorage before each spec
beforeEach(function() {
window.localStorage.clear();
})
// isInitial should return true because
// there is no localStorage variable set
it('should return true', function() {
var initial = SyncService.isInitial();
expect(initial).toBe(true);
});
// after creating the Initial variable using createInitial()
// we can test both functions
it('should call createInitial and isInitial should be true', function() {
SyncService.createInitial();
var initial = SyncService.isInitial();
console.log("initial: " + initial);
expect(initial).toBe(false);
});
});
I'm pretty new to angular and been wanting to test drive and I've hit a snag mocking out $window. The item in which I'm attempting to test is very simple but important -- I need to know if localStorage is there or not and need to be able to fake out $window to do so.
The code is very basic so far and what I have is this for the service ...
'use strict';
mainApp.factory('somedataStorage',function($window) {
var local = $window.localStorage;
return {
hasLocal: function() {
return local != undefined;
},
};
});
How I'm testing it is this ...
(function () {
var fakeWin = {
localStorage: null
};
var $injector = angular.injector(['ngMock', 'ng', 'mainApp']);
//var $window = $injector.get('$window');
var init = {
setup: function () {
//this.$window = fakeWin;
},
}
module('LocalStorageTests', init);
test("if localstorage isn't there, say so", function () {
var $service = $injector.get('somedataStorage' /*, {$window: fakeWin} */);
ok(!$service.hasLocal, "no local storage");
});
})();
So what am I missing?
The unittest:
"use strict";
var usersJSON = {};
describe("mainT", function () {
var ctrl, scope, httpBackend, locationMock,
beforeEach(module("testK"));
beforeEach(inject(function ($controller, $rootScope, $httpBackend, $location, $injector) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
locationMock = $location;
var lUrl = "../solr/users/select?indent=true&wt=json",
lRequestHandler = httpBackend.expect("GET", lUrl);
lRequestHandler.respond(200, usersJSON);
ctrl = $controller("mainT.controller.users", { $scope: scope, $location: locationMock});
httpBackend.flush();
expect(scope.users).toBeDefined();
}));
afterEach(function () {
httpBackend.verifyNoOutstandingRequest();
httpBackend.verifyNoOutstandingExpectation();
});
describe("method test", function () {
it('should test', function () {
expect(true).toBeFalsy();
});
});
});
controller I'm testing (working):
Asynchrone function in init who's giving me trouble (uses ../solr/users/select?indent=true&wt=json):
$scope.search = function () {
var lStart = 0,
lLimit = privates.page * privates.limit;
Search.get({
collection: "users",
start: lStart,
rows: lLimit)
}, function(records){
$scope.users= records.response.docs;
});
};
What I think happens:
1. inform backend what request he will receive
2. inform backend to response on that request with empty JSON
3. create a controller (Search.get get's executed)
4. inform backend to receive all requests and answer them (flush)
Yet I always get the following error:
Error: Unexpected request: GET : ../solr/users/select?indent=true&wt=json
Am I not handling the asynchrone search function well? how should this be done?
That's not really a "unit" test, it's more of a behavioral test.
This should really be a few tests:
Test your service Search.get to make sure it's calling the proper URL and returning the result.
Test your controller method to make sure it's calling Search.get
Test your controller method to make sure it's putting the result in the proper spot.
The code you've posted is a little incomplete, but here are two unit tests that should cover you:
This is something I've blogged about extensively, and the entries go into more detail:
Unit Testing Angular Controllers
Unit Testing Angular Services
Here's an example of what I'm talking about:
describe('Search', function () {
var Search,
$httpBackend;
beforeEach(function () {
module('myModule');
inject(function (_Search_, _$httpBackend_) {
Search = _Search_;
$httpBackend = _$httpBackend_;
});
});
describe('get()', function () {
var mockResult;
it('should call the proper url and return a promise with the data.', function () {
mockResult = { foo: 'bar' };
$httpBackend.expectGET('http://sample.com/url/here').respond(mockResult);
var resultOut,
handler = jasmine.createSpy('result handler');
Search.get({ arg1: 'wee' }).then(handler);
$httpBackend.flush();
expect(handler).toHaveBeenCalledWith(mockResult);
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.verifyNoOutstandingExpectation();
});
});
});
describe('myCtrl', function () {
var myCtrl,
$scope,
Search;
beforeEach(function () {
module('myModule');
inject(function ($rootScope, $controller, _Search_) {
$scope = $rootScope.$new();
Search = _Search;
myCtrl = $controller('MyCtrl', {
$scope: scope
});
});
});
describe('$scope.foo()', function () {
var mockResult = { foo: 'bar' };
beforeEach(function () {
//set up a spy.
spyOn(Search, 'get').andReturn({
then: function (fn) {
// this is going to execute your handler and do whatever
// you've programmed it to do.. like $scope.results = data; or
// something.
fn(mockResult);
}
});
$scope.foo();
});
it('should call Search.get().', function () {
expect(Search.get).toHaveBeenCalled();
});
it('should set $scope.results with the results returned from Search.get', function () {
expect(Search.results).toBe(mockResult);
});
});
});
In a BeforeEach you should use httpBackend.when instead of httpBackend.expect. I don't think you should have an assertion (expect) in your BeforeEach, so that should be moved to a separate it() block. I also don't see where lRequestHandler is defined. The 200 status is sent by default so that is not needed. Your httpBackend line should look like this:
httpBackend.when("GET", "/solr/users/select?indent=true&wt=json").respond({});
Your test should then be:
describe("method test", function () {
it('scope.user should be defined: ', function () {
expect(scope.user).toEqual({});
});
});
Your lUrl in the unit test, shouldn't be a relative path, i.e., instead of "../solr/users/select?indent=true&wt=json" it should be an absolute "/solr/users/select?indent=true&wt=json". So if your application is running at "http://localhost/a/b/index.html", lUrl should be "/a/solr/...".
Note that you can also use regular expressions in $httpBackend.expectGET(), that could be helpful here in case you are not entirely sure how the absolute path will look like later on.
I am just starting with angular and I wanted to write some simple unit tests for my controllers, here is what I got.
app.js:
'use strict';
// Declare app level module which depends on filters, and services
angular.module('Prototype', ['setsAndCollectionsService']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/dashboard', {templateUrl: 'partials/dashboard.html', controller: 'DashboardController'});
$routeProvider.when('/setsAndCollections', {templateUrl: 'partials/setsAndCollections.html', controller: SetsAndCollectionsController});
$routeProvider.when('/repetition', {templateUrl: 'partials/repetition.html', controller: RepetitionController});
$routeProvider.otherwise({redirectTo: '/dashboard'});
}]);
and controllers.js
'use strict';
/* Controllers */
var myApp = angular.module('Prototype');
myApp.controller('DashboardController', ['$scope', function (scope) {
scope.repeats = 6;
}]);
/*function DashboardController($scope) {
$scope.repeats = 5;
};*/
function SetsAndCollectionsController($scope, $location, collectionsService, repetitionService) {
$scope.id = 3;
$scope.collections = collectionsService.getCollections();
$scope.selectedCollection;
$scope.repetitionService = repetitionService;
$scope.switchCollection = function (collection) {
$scope.selectedCollection = collection;
};
$scope.addCollection = function () {
$scope.collections.push({
name: "collection" + $scope.id,
sets: []
});
++($scope.id);
};
$scope.addSet = function () {
$scope.selectedCollection.sets.push({
name: "set" + $scope.id,
questions: []
});
++($scope.id);
};
$scope.modifyRepetition = function (set) {
if (set.isSelected) {
$scope.repetitionService.removeSet(set);
} else {
$scope.repetitionService.addSet(set);
}
set.isSelected = !set.isSelected;
};
$scope.selectAllSets = function () {
var selectedCollectionSets = $scope.selectedCollection.sets;
for (var set in selectedCollectionSets) {
if (selectedCollectionSets[set].isSelected == false) {
$scope.repetitionService.addSet(set);
}
selectedCollectionSets[set].isSelected = true;
}
};
$scope.deselectAllSets = function () {
var selectedCollectionSets = $scope.selectedCollection.sets;
for (var set in selectedCollectionSets) {
if (selectedCollectionSets[set].isSelected) {
$scope.repetitionService.removeSet(set);
}
selectedCollectionSets[set].isSelected = false;
}
};
$scope.startRepetition = function () {
$location.path("/repetition");
};
}
function RepetitionController($scope, $location, repetitionService) {
$scope.sets = repetitionService.getSets();
$scope.questionsLeft = $scope.sets.length;
$scope.questionsAnswered = 0;
$scope.percentageLeft = ($scope.questionsLeft == 0 ? 100 : 0);
$scope.endRepetition = function () {
$location.path("/setsAndCollections");
};
}
now I am in process of converting global function controllers to ones defined by angular API as you can see by example of DashboardController.
Now in my test:
describe("DashboardController", function () {
var ctrl, scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('DashboardController', {$scope: scope});
}));
it("has repeats attribute set to 5", function () {
expect(scope.repeats).toBe(5);
});
});
I am getting
Error: Argument 'DashboardController' is not a function, got undefined
I am wondering then, where is my mistake? If I understand this right, ctrl = $controller('DashboardController', {$scope: scope}); should inject my newly created scope to my DashboardController to populate it with attributes - in this case, repeats.
You need to set up your Prototype module first.
beforeEach(module('Prototype'));
Add that to your test, above the current beforeEach would work.
describe("DashboardController", function () {
var ctrl, scope;
beforeEach(module('Prototype'));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('DashboardController', {$scope: scope});
}));
it("has repeats attribute set to 5", function () {
expect(scope.repeats).toBe(5);
});
});