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');
});
});
});
Related
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);
});
});
});
I'm trying to test a controller than requires me to mock a service that I'm using to get data. Currently I'm getting an error saying the function is undefined on this line:
dataServiceMock = jasmine.createSpyObj('dataService', ['getFunctionStuff']);
According to other examples and tutorials this should be working fine.
Here's my code including the test file, the service and the controller.
Controller:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope, dataService) {
dataService.getFunctionStuff($scope.foo)
.then(function(data) {
$scope.test = data;
});
});
Service:
app.factory('dataService', function ($timeout, $q){
function getFunctionStuff(formData) {
return $http.post('../randomAPICall', formData).then(function(data) {
return data;
});
};
});
Tests:
describe('Testing a controller', function() {
var $scope, ctrl, $timeout;
var dataServiceMock;
beforeEach(function (){
dataServiceMock = jasmine.createSpyObj('dataService', ['getFunctionStuff']);
module('myApp');
inject(function($rootScope, $controller, $q, _$timeout_) {
$scope = $rootScope.$new();
dataServiceMock.getFunctionStuff.and.ReturnValue($q.when('test'));
$timeout = _$timeout_;
ctrl = $controller('MainCtrl', {
$scope: $scope,
dataService: dataServiceMock
});
});
});
it('should update test', function (){
expect($scope.test).toEqual('test');
});
});
Here's a plunker of it: http://plnkr.co/edit/tBSl88RRhj56h3Oiny6S?p=preview
As you are using jasmine 2.1, the API is .and.returnValue. And in your test spec, do $scope.$apply() before then
describe('Testing a controller', function () {
var $scope, ctrl, $timeout;
var dataServiceMock;
beforeEach(function () {
dataServiceMock = jasmine.createSpyObj('dataService', ['getFunctionStuff']);
module('myApp');
inject(function ($rootScope, $controller, $q, _$timeout_) {
$scope = $rootScope.$new();
dataServiceMock.getFunctionStuff.and.returnValue($q.when('test'));
$timeout = _$timeout_;
ctrl = $controller('MainCtrl', {
$scope: $scope,
dataService: dataServiceMock
});
});
});
it('should update test', function () {
$scope.$apply();
expect($scope.test).toEqual('test');
});
});
Here is another common way to test $http by $httpBackend:
app.js
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope, dataService) {
dataService.getFunctionStuff($scope.foo)
.then(function(data) {
$scope.test = data.data;
});
});
dataService.js
app.factory('dataService', function($http) {
function getFunctionStuff(formData) {
return $http.post('../randomAPICall', formData).then(function(data) {
return data;
});
}
return {
getFunctionStuff: getFunctionStuff
};
});
specs.js
describe('Testing a controller', function() {
var $scope, ctrl, $controller, $httpBackend;
beforeEach(function (){
module('myApp');
inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$scope = $injector.get('$rootScope').$new();
$controller = $injector.get('$controller');
$scope.foo = 'foo';
$httpBackend.expectPOST('../randomAPICall', 'foo').respond(201, 'test');
ctrl = $controller('MainCtrl', {$scope: $scope});
});
});
it('should update test', function (){
expect($scope.test).not.toBeDefined();
$httpBackend.flush();
expect($scope.test).toEqual('test');
});
});
I'm trying to test the behavior of a 'watch' on my scope. But the problem is it's called only once. Check the fiddle below. And strangely, i noticed that if you put the variables in the controller's $scope rather than on the controller itself (aka $scope.name vs vm.name) it actually works.
Using vm (not working): http://jsfiddle.net/2Ny8x/60/
//--- CODE --------------------------
(function (angular) {
// Create module
var myApp = angular.module('myApp', []);
// Controller which counts changes to its "name" member
myApp.controller('MyCtrl', ['$scope', function ($scope) {
var vm = this;
vm.name = 'Superhero';
vm.counter = 0;
$scope.$watch('vm.name', function (newValue, oldValue) {
vm.counter = vm.counter + 1;
});
}]);
})(angular);
// ---SPECS-------------------------
describe('myApp', function () {
var scope,
controller;
beforeEach(function () {
module('myApp');
});
describe('MyCtrl', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(controller.name).toBe('Superhero');
});
it('watches the name and updates the counter', function () {
expect(controller.counter).toBe(0);
controller.name = 'Batman';
scope.$digest();
expect(controller.counter).toBe(1);
controller.name = 'Superman';
scope.$digest();
expect(controller.counter).toBe(2);
});
});
});
Using $scope (works): http://jsfiddle.net/2Ny8x/61/
//--- CODE --------------------------
(function (angular) {
// Create module
var myApp = angular.module('myApp', []);
// Controller which counts changes to its "name" member
myApp.controller('MyCtrl', ['$scope', function ($scope) {
$scope.name = 'Superhero';
$scope.counter = 0;
$scope.$watch('name', function (newValue, oldValue) {
$scope.counter = $scope.counter + 1;
});
}]);
})(angular);
// ---SPECS-------------------------
describe('myApp', function () {
var scope,
controller;
beforeEach(function () {
module('myApp');
});
describe('MyCtrl', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(scope.name).toBe('Superhero');
});
it('watches the name and updates the counter', function () {
expect(scope.counter).toBe(0);
scope.name = 'Batman';
scope.$digest();
expect(scope.counter).toBe(1);
scope.name = 'Superman';
scope.$digest();
expect(scope.counter).toBe(2);
});
});
});
You are watching for a property on the scope, which is not really the case, since you are modifying property in your controller. So change your watch to:-
$scope.$watch(function(){
return vm.name;
}, function (newValue, oldValue) {
vm.counter = vm.counter + 1;
});
Plnkr
Another way to make it work is to use controller as vm in your test while instantiating the controller, so that controller instance would be attached the scope as a property with name vm.
controller = $controller('MyCtrl as vm', {
'$scope': scope
});
Plnkr2
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);
});
});
});
I am unit testing a controller and I want to test an event handler. Say my controller looks like:
myModule.controller('MasterController', ['$scope', function($scope){
$scope.$on('$locationChangeSuccess', function() {
$scope.success = true;
});
}]);
Would I broadcast that in my Jasmine test? Would I emit it? Is there an accepted standard?
The solution I came up with is as follows:
describe('MasterController', function() {
var $scope, $rootScope, controller, CreateTarget;
beforeEach(function() {
inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
CreateTarget = function() {
$controller('MasterController', {$scope: $scope});
}
});
});
describe('$locationChangeSuccess', function() {
it('should set $scope.success to true', function() {
controller = CreateTarget();
$rootScope.$broadcast('$locationChangeSuccess');
expect($scope.success).toBe(true);
});
});
});
I don't think there is "an accepted standard" but according to $location source code the event is broadcasted, so I would mock this behavior and test it this way:
'use strict';
describe('MasterController', function() {
var MasterController,
$rootScope,
$scope;
beforeEach(module('myModule'));
beforeEach(inject(function($rootScope, $injector, $controller) {
$rootScope = $rootScope;
$scope = $rootScope.$new();
MasterController = $controller('MasterController', {
'$scope': $scope
});
$scope.$digest();
}));
describe('$locationChangeSuccess event listener', function() {
it('should set $scope.success to true', function() {
var newUrl = 'http://foourl.com';
var oldUrl = 'http://barurl.com'
$scope.$apply(function() {
$rootScope.$broadcast('$locationChangeSuccess', newUrl, oldUrl);
});
expect($scope.success).toBe(true);
});
});
});