I have a angularjs web application and want to use qunit for unit testing in it. I have a controller:
function RootCtrl($scope, $rootScope, $window, $location) {
// logger is empty at the start
$scope.logger = '';
// we have no login error at the start
$scope.login_error = '';
//
// Get values array of object
//
$rootScope.values = function (obj) {
var vals = [];
for( var key in obj ) {
if(key !== '$$hashKey' && key !== 'checked')
vals.push(obj[key]);
}
return vals;
}
}
Now i want to write unit test for values function with qunit. I included all js files to the test/index.html and qunit.css. Now my test.js has following content:
var injector = angular.injector(['ng', 'myApp']);
var init = {
setup : function () {
this.$scope = injector.get('$rootScope').$new();
}
}
module('RootCtrl', init);
test('RootCtrl', function(){
var $controller = injector.get('$controller');
$controller('RootCtrl', {
$scope : this.$scope,
$location : this.$location
});
equal(['value'], $controller.values({'key' : 'value'}))
});
But i'm getting error: http://docs.angularjs.org/error/$injector/unpr?p0=$rootElementProvider%20%3C-%20$rootElement%20%3C-%20$location%20%3C-%20$route at:
$controller('RootCtrl', {
$scope : this.$scope,
$location : this.$location
});
How to inject correctly controller and use $scope, $rootScope, $location and another services from it?
Thank you.
Try this instead of your controller
$controller('RootCtrl',['$scope', '$rootScope', '$location','$route', function ($scope, $rootScope, $location, $route) {
$scope : this.$scope,
$location : this.$location
}]);
Had similar problem, so since no other answer here.
I ended up using:
client side code:
var myApp= angular.module('myApp', []);
myApp.controller('myCtrl', function ($scope) {
//angular client side code
$scope.canSubmit = function () {
//some logic
return true;
}
}
Qunit tests:
var ctrl, ctrlScope, injector;
module("Testing the controller", {
setup: function () {
angular.module('myApp');
injector = angular.injector(['ng', 'myApp']);
ctrlScope = injector.get('$rootScope').$new();
ctrl = injector.get('$controller')('myCtrl', { $scope: ctrlScope });
ctrlScope.model = {
//model object
};
},
teardown: function () {
}
});
test("Given something happened then allow submit", function () {
ok(ctrlScope.someFunction(...), "some functionality happened");
equal(true, ctrlScope.canSubmit());
});
This blog post was useful.
One can easily inject more into the controller under test.
Related
I've moved this 'reports' feature from a single module (called 'aam') into the core, so that other modules (such as 'bbc') can use it.
Now I'm rewriting the unit test(s).
The grunt error I'm getting is
should go state aam.reports with URL_NOT_SPECIFIED
reports-state spec
TypeError: 'null' is not an object
(evaluating 'BbpcConfiguration.getProperty(configProperty).then')
which indicates to me that $state is empty or not structured correctly.
Here is the report controller:
(function() {
'use strict';
angular.module('com.ct.bbpcCore')
.controller('reportController', ['$window', '$state', 'BbpcUserService', 'BbpcConfiguration', function ($window, $state, BbpcUserService,BbpcConfiguration) {
angular.element(document).ready(function () {
//Get url base on locale
var reportUrl = "URL_NOT_SPECIFIED";
var currentState = $state.current.name;
var configProperty = "";
var title = "";
if (currentState.indexOf('aam.reports')) {
configProperty = 'report.aam.link';
title = "AAM.REPORT";
};
if (currentState.indexOf('bbc.reports')) {
configProperty = 'report.bbc.link';
title = "BBC.REPORT";
};
BbpcConfiguration.getProperty(configProperty).then(function(response) {
if (response) {
var language = BbpcUserService.getLanguageCd() || "en_CA";
reportUrl = response[language] || reportUrl;
}
var spec = "width=" + $window.outerWidth + ", height=" + $window.outerHeight;
$window.open(reportUrl, title, spec);
});
});
}]);
}());
And here is report-controller.spec:
describe('reports-state spec', function() {
'use strict';
var $injector, $window, $rootScope,
$state, BbpcConfiguration, reportController, $controller, BbpcUserService;
beforeEach(function() {
module('com.ct.bbpcCore', function($provide) {
$provide.value('BbpcConfiguration', BbpcConfiguration = {
getProperty: function(key){
if('report.aam.link' === key){
return {
"fr_CA": "https://eng-link",
"en_CA": "https://fre-link"
};
}
return null;
}
});
});
inject(function(_$injector_) {
$injector = _$injector_;
$window = $injector.get('$window');
$state = $injector.get('$state');
$rootScope = $injector.get('$rootScope');
$controller =$injector.get('$controller');
BbpcUserService =$injector.get('BbpcUserService');
});
});
it('should go state aam.reports with URL_NOT_SPECIFIED', function() {
$state.current = {'name': 'aam.reports' };
spyOn($window, 'open').andCallFake(function(){});
reportController = $controller('reportController', {'$window':$window, '$state':$state, 'BbpcUserService':BbpcUserService, 'reportLink':undefined});
$state.go('aam.reports');
$rootScope.$apply();
expect($state.current.name).toEqual('aam.reports');
expect($window.open).toHaveBeenCalledWith('URL_NOT_SPECIFIED', 'AAM.REPORT', 'width=0, height=0');
});
});
I tried simply adding the line $state.current = {'name': 'aam.reports' }; in the 'it' block, but that's not what it's looking for.
Not sure how to debug unit tests. :P I can't use a console.log($state) to peek into it.
I called one $mdDialog inside a function. I want to unit-test $mdDialog ok and cancel cases.
The below is my controller code (app.controller.js).
(function () {
'use strict';
app.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$mdDialog'];
function AppCtrl($scope, $mdDialog) {
$scope.saveEntry = function (ev) {
var confirm = $mdDialog.prompt()
.title('Save Entry')
.textContent('If you want, you can add a description to explain what you changed.')
.placeholder('Version Description')
.ariaLabel('Version Description')
.initialValue('')
.targetEvent(ev)
.ok('Save')
.cancel('Cancel');
$mdDialog.show(confirm).then(function (result) {
$scope.status = true;
}, function () {
$scope.status = false;
});
};
}
})();
The following is the spec code (app.controller.spec.js) :
describe('Unit test AppController: mdDialog', function () {
var $controller, $mdDialog;
beforeEach(function () {
module('App');
inject(function (_$controller_, _$mdDialog_) {
$controller = _$controller_;
$mdDialog = _$mdDialog_;
});
});
it(': Opened', function () {
var $scope = {};
var controller = $controller('AppCtrl', { $scope: $scope });
var $mdDialogOpened = false;
$mdDialog.show = jasmine.createSpy().and.callFake(function () {
$mdDialogOpened = true;
});
$scope.saveEntry();
$scope.$digest();
expect($mdDialog.show).toHaveBeenCalled;
expect($mdDialogOpened).toBe.true;
});
});
when I running the above code I'm getting the following error:
TypeError: Cannot read property 'then' of undefined
I referred this GitHub issue https://github.com/angular/material/issues/1482. But I'm not getting solution for my problem
Thanks in advance
The problem is that you are injecting one version of $mdDialog, and trying to test on another one.
You could try something like this:
describe('Unit test AppController: mdDialog', function () {
var ctrl, mdDialog, scope;
beforeEach(function () {
module('App');
inject(function ($rootScope, $controller, $mdDialog) {
scope = $rootScope.$new();
mdDialog = $mdDialog; //keep the reference, for later testing.
spyOn(mdDialog, 'show');
mdDialog.show.and.callFake(function () {
return {
then: function (callBack) {
callBack(true); //return the value to be assigned.
}
}
});
ctrl = $controller('AppCtrl',{$scope:scope, $mdDialog:mdDialog}); //Inject the dependency
});
});
it(': Opened', function () {
scope.saveEntry(); //exercise the method.
scope.$digest();
expect(mdDialog.show).toHaveBeenCalled();
expect(scope.status).toBe(true);
});
});
Something very similar should work.
hope this help.
OK, I've built services before but obviously I don't actually know what makes them tick, since I can't seem to debug this ultra-simple service call:
app.js:
var gridApp = angular.module('gridApp', []);
gridApp.controller('mainController', ['$scope', '$http', 'dataService',
function($scope, dataService) {
$scope.message = 'I am Angular and I am working.';
var init = function(){
console.log(dataService.foo);
console.log(dataService.getData());
};
init();
}]);
dataService.js:
(function() {
'use strict';
angular
.module('gridApp')
.service('dataService', dataService)
dataService.$inject = [];
function dataService() {
console.log("I am the dataService and I am loaded");
var foo = 1;
function getData () {
return 2;
}
}
})();
I see this on-screen: I am Angular and I am working. so Angular is loading.
I see this in console: I am the dataService and I am loaded so the dataService is actually being loaded.
But then the console.log is:
undefined (line 8)
TypeError: dataService.getData is not a function (line 9)
What am I missing?
The previous answers are correct in that your $http injection was wrong, but you are also not attaching your service functions to the service:
function dataService() {
var dataService = this; //get a reference to the service
//attach your functions and variables to the service reference
dataService.foo = 1;
dataService.getData = function() {
return 2;
};
}
An Angular service is very simply an object class. It is also a singleton, meaning it's instantiated only once per run of your app. When the service is instantiated it is very much akin to calling the new operator on your dataService "class":
var $dataService = new dataService();
So, when you inject dataService into your controller, you are actually getting an instance, $dataService, of your dataService "class".
See this blog entry for further reading: https://tylermcginnis.com/angularjs-factory-vs-service-vs-provider-5f426cfe6b8c#.sneoo52nk
You are missing the 2nd parameter $http in the function. The named parameters and the actual parameters in function need to be the same, same order and same number. What happened before is that dataService was being assigned an $http instance and the actual dataService was not injected at all because there was no 3rd parameter to inject it into.
var gridApp = angular.module('gridApp', []);
gridApp.controller('mainController', ['$scope', '$http', 'dataService',
function($scope, $http, dataService) {
// ----was missing-----^
$scope.message = 'I am Angular and I am working.';
var init = function(){
console.log(dataService.foo);
console.log(dataService.getData());
};
init();
}]);
We have missed the second param '$http' in function. Just add the '$http' param, it should work fine
var gridApp = angular.module('gridApp', []);
gridApp.controller('mainController', ['$scope', '$http', 'dataService',
function($scope,$http, dataService) {
$scope.message = 'I am Angular and I am working.';
var init = function(){
console.log(dataService.foo);
console.log(dataService.getData());
};
init();
}]);
This is how I've been taught to set up services:
function dataService() {
var dataService = {};
var _foo = 1;
var _getData = function () { return 2; }
dataService.foo = _foo;
dataService.getData = _getData;
return dataService;
}
I believe this facilitates public/private methods/vars.
For reference, this is the full code accessing my service:
app.js:
var gridApp = angular.module('gridApp', []);
// create the controller and inject Angular's $scope
gridApp.controller('mainController', ['$scope', 'dataService', function($scope, dataService) {
// create a message to display in our view
$scope.message = 'Angular is working';
var init = function(){
getPackageData();
};
var getPackageData = function (){
return dataService.getData().then(
function successCallback(response) {
console.log(response);
},
function errorCallback(response) {
console.log(response);
}
);
};
init();
}]);
dataService.js:
(function() {
'use strict';
angular
.module('gridApp')
.service('dataService', dataService)
dataService.$inject = ['$http'];
function dataService($http) {
var dataService = {};
var _getData = function () {
return $http({
method: 'GET',
url: 'data/packages.json'
})
.then(function successCallback(response) {
return response;
},
function errorCallback(response) {
return response;
});
}
dataService.getData = _getData;
return dataService;
}
})();
I have a module with a service defined:
var ngError = angular.module('ngError', []);
ngError.service('ErrorService', ['$scope', function($scope) {
$scope.displayErrors = function(errors) {
alert(errors);
}
}]);
Then I have another module:
var ngLogin = angular.module('ngLogin', ['ngError']);
Which has a controller that attempts to use the first service defined on ngError:
ngLogin.controller('LoginCtrl', ['$scope', 'LoginService', 'ErrorService', function($scope, LoginService, ErrorService) {
$scope.user = {};
$scope.user.id = 0;
$scope.user.token = '';
$scope.login = function(callback) {
LoginService.login($scope.user.username, $scope.user.password, function(token) {
$scope.setToken(token);
$scope.$apply();
if (typeof callback === 'function') {
callback(callback);
}
}, function(errors) {
ErrorService.displayErrors(errors);
});
};
}]);
But some reason this is throwing the following error:
Unknown provider: $scopeProvider <- $scope <- ErrorService
You cannot use $scope within a service. Change the service as follows and it will work:
var ngError = angular.module('ngError', []);
ngError.service('ErrorService', [function() {
this.displayErrors = function(errors) {
alert(errors);
}
}]);
Try like this , working best for me
var app = angular.module('MyApp', []);
app.service('MyService', function () {
var property = 'First';
this.myFunc = function (x) {
return x*5;
}
});
//
controller 2 under second app module
var app = angular.module('AnotherApp',[]);
app.controller("AnotherAppCtrl", function($scope,MyService) {
$scope.value = MyService.myFunc(4);
});
I am a Jasmine rookie and trying to figure out how to mock the window.user.username object when testing an angularjs using Jasmine.
var applicationUser = window.user.username;
I just figured out, Not Sure, if this is the right way. But it worked. Hope it helps.
//Main Function
function MyController($scope, $window) {
$scope.HeaderCtrl = function() {
$scope.user = $window.user;
---
}
//Jasmine Code
var $window;
beforeEach(inject(function(_$window_) {
$window = _$window_;
$window.user = 'xyz';
}));
createController = function() {
return $controller('EpicController', {
'$scope': $rootScope,
'$window': $window
});
};
Another way of doing it,
Mocking window in an angularJS test
you have to add user object to jasmine global space.
//Main Function
function MyController($scope, $window) {
$scope.HeaderCtrl = function() {
$scope.user = $window.user;
---
}
//Jasmine Code
var $window;
beforeEach(inject(function(_$window_) {
jasmine.getGlobal().$window = _$window_;
// OR
// here i am assuming that user class has only username property
jasmine.getGlobal().$windows = { "user": {"username": "xyz"}};
$window.user = 'xyz';
}));
createController = function() {
return $controller('EpicController', {
'$scope': $rootScope,
'$window': $window
});
};