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);
});
});
});
Related
I make one controller in Angularjs and try to test that controller using Jasmine. I got this error Cannot read property 'message' of undefined why ?
Here is my code.
controller
(function(){
'use strict';
angular.module('app.home').controller('homeCntrl',homeCntrl);
function homeCntrl(){
var home=this;
home.clickbtn=function(){
home.message='test';
alert(home.message)
}
}
})();
Testing
(function(){
'use strict';
describe('http controller test', function() {
var $rootScope,
$scope,
controller,
$q,
$httpBackend;
beforeEach(function() {
module('app');
inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
controller = $injector.get('$controller')('homeCntrl', {
$scope: $scope
})
})
})
describe('Init value', function() {
it('check name value', function() {
expect(controller.message).toBeUndefined();
})
})
it('it should be true', function() {
expect(true).toBeTruthy();
})
})
})();
any update ?durning testing I got this error .. ? can we do testing of this controller ?Every thing is fine on angular js code problem is on test code..only check appspec.js
Just an hint
app
(function() {
'use strict';
function HomeController() {
var home = this;
home.title = 'Home';
}
angular.module('home.controllers', [])
.controller('HomeController', HomeController);
})();
test
'use strict';
describe('home controller', function() {
var $controller;
var scope;
beforeEach(module('home.controllers'));
beforeEach(inject(function(_$controller_, $rootScope) {
$controller = _$controller_;
scope = $rootScope.$new();
$controller('HomeController as home', {$scope: scope});
}));
it('should have text = "Home"', function() {
expect(scope.home.title).toEqual('Home');
});
});
in your case the test should be like
scope.home.clickbtn();
expect(scope.home.message).toEqual('test');
Take a look at http://www.bradoncode.com/tutorials/angularjs-unit-testing/ to master unit test in angular
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');
});
});
});
Controller:
angular.module('ngApp', [
'templates-app',
'templates-common',
'ngApp.home',
'ui.router'
])
.config(function myAppConfig($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
})
.run(function run() {})
.controller('AppCtrl', function AppCtrl($scope) {
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
if (angular.isDefined(toState.data.pageTitle)) {
$scope.pageTitle = toState.data.pageTitle + ' | App';
}
});
});
Test:
describe('AppCtrl', function() {
beforeEach(module('ngApp'));
var $controller;
beforeEach(inject(function(_$controller_) {
$controller = _$controller_;
}));
describe('$scope.pageTitle', function() {
it('should set pageTitle', inject(function() {
var $scope = {};
var AppCtrl = $controller('AppCtrl', { $scope: $scope });
expect(true).toBe(true);
}));
});
});
I am not testing anything right now, just loading the controller. However this fails
Chrome 41.0.2272 (Mac OS X 10.10.2) AppCtrl $scope.pageTitle should set pageTitle FAILED
TypeError: undefined is not a function
at new AppCtrl (/src/app/app.js:15:9)
at invoke (/vendor/angular/angular.js:4203:17)
at Object.instantiate (/vendor/angular/angular.js:4211:27)
at /vendor/angular/angular.js:8501:28
at /vendor/angular-mocks/angular-mocks.js:1878:12
at Object.<anonymous> (/src/app/app.spec.js:14:19)
at Object.invoke (/vendor/angular/angular.js:4203:17)
at Object.workFn (/vendor/angular-mocks/angular-mocks.js:2436:20)
Error: Declaration Location
at window.inject.angular.mock.inject (/vendor/angular-mocks/angular-mocks.js:2407:25)
at Suite.<anonymous> (/src/app/app.spec.js:12:30)
at Suite.<anonymous> (/src/app/app.spec.js:11:2)
at /src/app/app.spec.js:1:1
Any ideas whats wrong with my test setup?
Actual test:
describe('$scope.pageTitle', function() {
it('should be defiend after stateChange', inject(function() {
var $scope = {};
var AppCtrl = createController('AppCtrl');
scope.$broadcast('$stateChangeSuccess', {
data: {
pageTitle: 'bar'
}
});
expect(scope.pageTitle).toBeDefined();
}));
});
You need to create the scope from the $rootScope and inject it:
beforeEach(inject(function (_$controller_, $rootScope) {
$controller = _$controller_;
scope = $rootScope.$new();
createController = function (ctrlName) {
return $controller(ctrlName, {
'$scope': scope
});
};
}));
In this way you can use the createController function for the purpose and using:
var AppCtrl = createController('AppCtrl');
For having the controller.
Here is a working jsfiddle:
http://jsfiddle.net/Lbs6sk0k/2/
EDIT: where is $scope empty?
if I put a console.log here:
.controller('AppCtrl', function AppCtrl($scope) {
console.log($scope);
I can see this:
EDIT 2:
Set the the scope as a global variable:
describe('AppCtrl', function () {
beforeEach(module('ngApp'));
var $controller;
var scope;
Then, in you test case, you can use it:
it('should set pageTitle', inject(function () {
var $scope = scope;
Updated jsfiddle with your new test case: http://jsfiddle.net/Lbs6sk0k/3/
I think the problem is with the scope you're injecting into the controller and the $scope.$on... . Try this:
describe('AppCtrl', function() {
beforeEach(module('ngApp'));
var $controller, $scope;
beforeEach(inject(function(_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_.$new();
}));
describe('$scope.pageTitle', function() {
it('should set pageTitle', inject(function() {
var AppCtrl = $controller('AppCtrl', { $scope: $scope });
expect(true).toBe(true);
}));
});
});
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 have the following...
app.controller('testCtrl', function(testService){
testService.doSomething();
});
app.service('testService', function(){
this.doSomething = function(){...};
});
I want to use Jasmine to ensure doSomething is called once and only once. I seem to be having some trouble doing this.
Also, I am currently grabbing my controller from a compiled element like this...
var element = angular.element('<my-test-directive />');
controller = view.controller('testCtrl');
So extra appreciation if it fits with this sort of formatting
Update
I tried this...
describe("Testing", function () {
var $rootScope,
$scope,
$compile,
testService,
view,
$controller;
beforeEach(module("app"));
function createController() {
return $controller('testCtrl', {
$scope: scope,
testService:testService
});
}
function SetUpScope(_$controller_, _$compile_, _$rootScope_, _testService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$controller = _$controller_;
testService = _testService_;
spyOn(testService, 'doSomething');
}
SetUpScope.$inject = ["$controller","$compile", "$rootScope", "testService"];
beforeEach(inject(SetUpScope));
it("On intitialization, the controller should register itself with the list service", function(done){
createController();
scope.$digest();
expect(workOrderService.doSomething).toHaveBeenCalled();
})
});
It seems to work
It is probably better to test controller in isolation and use Jasmine spies for this:
spyOn(testService, 'doSomething');
expect(testService.doSomething.calls.count()).toEqual(0);
Something like this should work in the actual test.
describe('testCtrl function', function() {
describe('testCtrl', function() {
var $scope, testService;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller, _testService_) {
$scope = $rootScope.$new();
testService = _testService_;
spyOn(testService, 'doSomething');
$controller('MyController', {$scope: $scope});
}));
it('should call testService.doSomething()', function() {
expect(testService.doSomething.calls.count()).toEqual(1);
});
});
});
Here is a quick plunkr http://plnkr.co/edit/Swso4Y
Depending on which version of Jasmine you are using you might need to use
expect(testService.doSomething.calls.length).toEqual(1);