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
Related
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');
});
});
});
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 am using latest Karma Angular.js unit testing.
I have an error: 'Argument 'appCtrl' is not a function, got undefined'.
appCtrl.js:
var app = angular.module('app', []);
app.controller('appCtrl', function ($scope) {
$scope.count = 5;
$scope.incrementCount = function() {
$scope.count = $scope.count + 1;
};
});
appCtrl.spec.js:
describe('Controller test', function(){
var appCtrl, $scope;
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope) {
$scope = $rootScope.$new();
appCtrl = $controller('appCtrl', {
$scope: $scope
});
}));
it('should have appCtrl controller toBeDefined', function() {
expect(appCtrl).toBeDefined();
});
iit('should init counter value', function() {
expect($scope.count).toBeDefined();
expect($scope.count).toBe(5);
});
it('should change counter value', function() {
$scope.incrementCount();
expect($scope.count).toBe(6);
});
});
What I am doing wrong?
Make sure you have the angular-mocks dependency, and also you have a typo 'itt' should be 'it' in the should init counter value.
Hope this helps.
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);
});
});
});
I have this simple controller, UserService is a service which return JSON
"use strict";
angular.module("controllers").controller('profileCtrl', ["$scope", "UserService",
function ($scope, UserService) {
$scope.current_user = UserService.details(0);
}
]);
I can not make the test. However this is my try
'use strict';
describe('profileCtrl', function () {
var scope, ctrl;
beforeEach(angular.mock.module('controllers'), function($provide){
$provide.value("UserService", {
details: function(num) { return "sdfsdf"; }
});
});
it('should have a LoginCtrl controller', function() {
expect(controllers.profileCtrl).toBeDefined();
});
beforeEach(angular.mock.inject(function($rootScope, $controller){
scope = $rootScope.$new();
$controller('profileCtrl', {$scope: scope});
}));
it('should fetch list of users', function(){
expect(controllers.scope.current_user.length).toBe(6);
expect(controllers.scope.current_user).toBe('sdfsdf');
});
});
The usage of $controller is correct, that's the way to instantiate a controller for a unit test. You can mock the UserService instance it gets directly in the $controller invocation.
You should be using its return value - this is the instance of your controller you're going to test.
You're trying to read stuff from controllers but its not defined anywhere in the test, I guess you're referring to the module.
This is how I would go about it + fiddle
//--- CODE --------------------------
angular.module('controllers', []).controller('profileCtrl', ["$scope", "UserService",
function ($scope, UserService) {
$scope.current_user = UserService.details(0);
}]);
// --- SPECS -------------------------
describe('profileCtrl', function () {
var scope, ctrl, userServiceMock;
beforeEach(function () {
userServiceMock = jasmine.createSpyObj('UserService', ['details']);
userServiceMock.details.andReturn('sdfsdf');
angular.mock.module('controllers');
angular.mock.inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('profileCtrl', {
$scope: scope,
UserService: userServiceMock
});
});
});
it('should have a LoginCtrl controller', function () {
expect(ctrl).toBeDefined();
});
it('should fetch list of users', function () {
expect(scope.current_user).toBe('sdfsdf');
});
});
You're welcome to change the fiddle online to see how it affects testing results.