How to inject controller dependencies in Jasmine tests? - angularjs

There is the following controller definition:
angular.module('app.controllers', []).controller('HomeController', [
'$scope', '$modal', 'Point', function($scope, $modal, Point) { //some action }
I want to test this controller:
describe('HomeController', function() {
beforeEach(module('app.controllers'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
var $scope = {};
var controller = $controller('HomeController', { $scope: $scope });
$scope.label = '12345';
$scope.addNewPoint();
expect($scope.label).toEqual(null);
});
});
});
"Point" is my custom service, "$modal" is Angular Bootstrap module. How can I inject it in my tests? Thanks in advance!

The services should be automatically injected. If you wish to mock them or spy on them, inject them like so:
describe('HomeController', function() {
beforeEach(module('app'));
var $controller, $scope, $modal, Point;
beforeEach(inject(function(_$controller_, _$rootScope_, _$modal_, _Point_){
$scope = $rootScope.$new();
$modal = _$modal_;
Point = _Point_;
spyOn($modal, 'method');
spyOn(Point, 'method');
$controller = _$controller_('HomeController', { $scope: $scope, $modal: $modal, Point: Point });
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
$scope.label = '12345';
$scope.addNewPoint();
expect($scope.label).toEqual(null);
});
});
});

Related

Error: Injector already created, can not register a module

I am trying to test if window.location is set to a specific URL by end of the method, but I get this error:
Error: Injector already created, can not register a module!
The code:
describe('Home controller', function() {
var $controller, $location, $window, $http, $timeout, $filter, $scope, $resource;
beforeEach(module('app', function($provide) {
$provide.value('$window', {
location: {
href: ''
}
});
}));
beforeEach(inject(function(_$controller_, _$location_, _$window_, _$rootScope_, _$http_,
_$resource_, _$timeout_, _$filter_) {
$controller = _$controller_;
$location = _$location_;
$window = _$window_;
$http = _$http_;
$timeout = _$timeout_;
$filter = _$filter_;
$scope = _$rootScope_.$new();
}));
it('check Home Ctrl', inject(function($rootScope, $httpBackend, API_URL) {
var ctrlInstance = $controller('HomeCtrl', {
$scope: $scope,
$rootScope: $rootScope,
$http: $http,
$resource: $resource,
$location: $location,
$window: $window,
$timeout: $timeout,
API_URL: API_URL
});
$scope.goEditUser({
userId: 2
});
expect($window.location.href).toContain('/switch-user/2');
}));
});
Why am I getting the error even when inject is called after module?
You can try to use single inject method :
var ctrlInstance;
beforeEach(module('app');
beforeEach(module(function($provide) {...})
and something like this
it('check Home Ctrl', inject(function($controller, _$location_, _$window_, _$rootScope_, _$http_, _$resource_, _$timeout_, _$filter_) {
var ctrlInstance = $controller('HomeCtrl', {
$location : _$location_,
$window : _$window_,
$http : _$http_,
$timeout : _$timeout_,
$filter : _$filter_,
$scope : _$rootScope_.$new(),
$rootScope: _$rootScope_,
$resource: _$resource_,
API_URL: API_URL
});
$scope.goEditUser({
userId: 2
});
expect($window.location.href).toContain('/switch-user/2');
}));

angular JS unit testing a controller

I have a controller like this
(function(){
var app = angular.module('app', []);
app.directive('test', function(){
return {
restrict: 'E',
templateUrl: 'test.html',
controller: ['$scope', function ($scope) {
$scope.password = '';
$scope.grade = function() {
var size = $scope.password.length;
if (size > 8) {
$scope.strength = 'strong';
} else if (size > 3) {
$scope.strength = 'medium';
} else {
$scope.strength = 'weak';
}
}
}];
});
I am writing a unit test to this controller
describe('PasswordController', function() {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
var $scope = {};
var controller = $controller('$scope', { $scope: $scope });
$scope.password = 'longerthaneightchars';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});
I am ending up getting an error which says
Error:[ng:areq] Argument '$scope' is not a function, got undefined
I am I going in the right way please help
Your controller is defined as a part of your directive definition, and I do not believe that these can be unit tested independently of the directive themsleves.
If you want to unit test this controller, you should give it a separate name using angular's controller method, then use it in your directive by name. Then you can retrieve the controller using angular-mock's $controller service similar to how you do it now. the end result looks like:
app.controller('YourCtrl', ['$scope', function($scope) { ... }]);
app.directive('test', function() {
return {
...
controller: 'YourCtrl',
...
}});
and in the test
var controller = $controller('YourCtrl', { $scope: $scope });
Here is a jsFiddle that puts it all together
Here is how I would test the directive's controller. DEMO http://plnkr.co/edit/w9cJ6KDNDvemO8QT3tTN?p=preview
I would not import the controller. I would compile the directive and test the directive's controller.
describe('PasswordController', function() {
var $scope;
var element;
beforeEach(module('MyApp'));
beforeEach(
inject(function($rootScope, $compile, $templateCache) {
// Imports test.html
var templateUrl = 'test.html';
var req = new XMLHttpRequest();
req.onload = function () {
$templateCache.put(templateUrl, this.responseText);
};
req.open('get', templateUrl, false);
req.send();
$scope = $rootScope.$new();
element = '<test></test>';
// Compile the directive
element = $compile(element)($scope);
// Call digest cycle
$scope.$apply();
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
$scope.password = 'longerthaneightchars';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});
You can't create a $scope by doing $scope = {}. Change your spec to this:
describe('PasswordController', function () {
beforeEach(module('app'));
var $controller, $rootScope;
beforeEach(inject(function (_$controller_, _$rootScope_) {
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('$scope.grade', function () {
it('sets the strength to "strong" if the password length is >8 chars', function () {
var $scope = $rootScope.$new();
var controller = $controller('$scope', {
$scope : $scope
});
$scope.password = 'longerthaneightchars';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});

jasmine test $scope is undefinded

I try to write a controller test using karma with jasmine.
I get this error "Error: [$injector:unpr] Unknown SettingsProvider <- settings"
I been stuck for hours googling around but I can't found a solution for this.
My test Case
describe('MyController', function() {
var $scope, controller;
beforeEach(module('MyApp'));
beforeEach(inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
controller = $controller('MyController', {
$scope: $scope
});
}));
it('sets the options to "valid" if the type is val', function() {
var type = 'val';
$scope.callOptions(type);
expect($scope.options).toBeTruthy();
});
});
My karma.config.js
files: [
'app/bower_components/angular/angular.js',
'app/bower_components/jquery/dist/jquery.min.js',
'app/node_modules/angular-mocks/angular-mocks.js',
'app/bower_components/angular-resource/angular-resource.js',
'app/bower_components/angular-ui-router/release/angular-ui-router.js',
'app/bower_components/angular-ui-router/release/angular-ui-router.min.js',
'app/metronic/assets/global/plugins/angularjs/plugins/angular-ui-router.min.js',
'app/bower_components/ui-router-extras/release/modular/ct-ui-router-extras.sticky.js',
'app/bower_components/ngDraggable/ngDraggable.js',
'app/metronic/assets/global/plugins/angularjs/angular-sanitize.min.js',
'app/metronic/assets/global/plugins/jquery.min.js',
'app/metronic/assets/global/plugins/bootstrap/js/bootstrap.min.js',
'app/metronic/assets/global/plugins/angularjs/plugins/ui-bootstrap-tpls.min.js',
'app/bower_components/ngstorage/ngStorage.min.js',
'app/bower_components/oclazyload/dist/ocLazyLoad.min.js',
'app/metronic/assets/global/plugins/angularjs/plugins/angular-file-upload/angular-file-upload.min.js',
'app/js/services/myProvider.js',
'app/js/app.js',
'app/controllers/MyController.js'
]
My controller :
MetronicApp.controller('MyController',
['$http',
'$rootScope',
'$scope',
'$window',
function ($http, $rootScope, $scope, $window) {
$scope.callOptions = function (type) {
if (type == 'val') {
return $scope.optionsVal;
}
;
if (type == 'Num') {
return $scope.optionsNum;
}
;
};
});
EDIT
I delete some file from my karma.config.js and now I get this error $scope is undefined ..This is a screen shot of the error :
I think the way you injected $rootScope and $controller is incorrect.Please try following code snippet.It will do the thing.
describe('MyController', function() {
var $scope, controller;
beforeEach(module('MyApp'));
beforeEach(inject(function ($injector) {
$scope = $injector.get("$rootScope").$new();
controller = $injector.get("$controller")('MyController', {
$scope: $scope
});
}));
it('sets the options to "valid" if the type is val', function() {
var type = 'val';
$scope.callOptions(type);
expect($scope.options).toBeTruthy();
});
});

AngularJs Testing Sinon spy

I am new with Sinon so I wanted to check whether a specific function is being called, this is what I got:
terminalController.controller('CashAcceptorController', [
'PaymentService',
'$rootScope',
'$scope',
'PayingInfo',
'$interval',
'$location',
function (PaymentService, $rootScope, $scope, PayingInfo, $interval, $location) {
PaymentService.start();
....
]);
In tests, I try to check that PaymentService.start() is called on controller instantiation:
describe('CashAcceptorController', function() {
var PaymentService, rootScope, scope, PayingInfo, $interval, $location;
var mySpy = sinon.spy(PaymentService.start());;
beforeEach(module('eshtaPayTerminalApp.controllers'));
beforeEach(module('eshtaPayTerminalApp.services'));
beforeEach(inject(function($controller,
$rootScope, _PaymentService_, _$interval_, _PayingInfo_) {
$interval = _$interval_;
scope = $rootScope.$new();
rootScope = $rootScope.$new();
PaymentService = _PaymentService_;
PayingInfo = _PayingInfo_;
rootScope.serviceNumber = 'm1';
rootScope.phoneNumber = '05135309';
$controller('CashAcceptorController', {
$rootScope : rootScope,
$scope : scope,
$location : $location,
_PaymentService_ : PaymentService,
_$interval_:$interval,
_PayingInfo_:PayingInfo
});
}));
it('should call start paying', function() {
expect(mySpy.callCount).to.equal(1);
});
But this assertion fails. What am I doing wrong? Help please :)
There's a couple of issues with your code
The PaymentService object needs to be assigned before you can spy on it
To add a spy with sinon you need pass the method name as a string eg. sinon.spy(PaymentService, 'start');
I've created a working plunk of the above at http://plnkr.co/edit/AvqS3L?p=preview
Here's the updated test code:
describe('CashAcceptorController', function() {
var PaymentService;
var $controller;
beforeEach(module('eshtaPayTerminalApp.controllers'));
beforeEach(module('eshtaPayTerminalApp.services'));
beforeEach(inject(function(_PaymentService_, _$controller_) {
PaymentService = _PaymentService_;
$controller = _$controller_;
}));
it('should call start paying', function() {
var mySpy = sinon.spy(PaymentService, 'start');
$controller('CashAcceptorController', { PaymentService: PaymentService });
chai.expect(mySpy.callCount).to.equal(1);
// another way of checking that it was called once
chai.assert(PaymentService.start.calledOnce);
});
});

AngularJS + Jasmine Noob: How to access $scope in spec?

This is my first time testing using Jasmine. I'm having trouble accessing the $scope variables in the spec. I have a failing test:
mysite ProductsDetailCtrl sets hey
Expected undefined to be 1.
Error: Expected undefined to be 1.
spec:
//= require helpers/load-angular-mysite-module
//= require products/controllers/products_detail_controller
describe('mysite', function() {
var $rootScope, $scope, $controller;
beforeEach(function() {
module('mysite');
});
describe('ProductsDetailCtrl', function() {
beforeEach(inject(function(_$rootScope_, _$controller_) {
$rootScope = _$rootScope_; // don't really
$scope = $rootScope.$new(); // understand what's
$controller = _$controller_; // going on in this function
controller = $controller('ProductsDetailCtrl', {
'$rootScope': $rootScope,
'$scope': $scope
});
}));
it('sets hey', function() {
expect($rootScope.hey).toBe(1);
});
});
});
controller:
app.controller('ProductsDetailCtrl', ['$scope', '$resource', function($scope, $resource) {
$scope.hey = 1;
....
Could someone explain to me how I would access the scope?
You just have to check for the property heyin your $scope not in the $rootScope:
describe('mysite', function() {
var scope, ProductsDetailCtrl;
beforeEach(function() {
module('mysite');
});
describe('ProductsDetailCtrl', function() {
beforeEach(inject(function($controller, $rootScope) {
// Create a mock scope for your controller.
scope = $rootScope.$new();
// Initialize the controller with the mocked scope.
ProductsDetailCtrl = $controller('ProductsDetailCtrl', {
$scope: scope
});
}));
it('sets hey', function() {
expect(scope.hey).toBe(1);
});
});
});

Resources