I'm trying to write unit-tests for an Angular application for the first time. Currently i'm having some problems running the tests. Running the application normally works fine, it doesn't give any errors. However, when running the tests using Karma and Jasmine i'm getting the following error:
TypeError: 'undefined' is not a function (evaluating '$scope.addActiveClassToMenuButton('menuButtonHome')')
I'm using the ui.router module. Not sure if that matters.
Parent controller
Parent controller contains the following method:
angular.module('testApp')
.controller('ParentCtrl', function ($scope, $resource) {
$scope.addActiveClassToMenuButton = function(buttonId) {
//Some code
}
}
Child controller
Child controller calls the parents method like this:
angular.module('testApp')
.controller('ChildCtrl', function ($scope, $resource) {
$scope.addActiveClassToMenuButton('menuButtonHome');
}
Child controller test file
The test file that fails:
describe('Child controller tests. ', function () {
beforeEach(module('testApp'));
var ChildCtrl, scope;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
ChildCtrl = $controller('ChildCtrl', {
$scope: scope
});
}));
it('simple false test', function () {
expect(false).toBe(false);
});
});
Even though i'm not using the scope in the test yet, all tests fail because the code can't find the parents method.
Solution
Changing the test file to this worked:
describe('Child controller tests. ', function () {
beforeEach(module('testApp'));
var controller, scope, parentScope, childScope;
beforeEach(inject(function ($controller, $rootScope, $compile) {
scope = $rootScope.$new();
var el = angular.element('<div ng-controller="ParentCtrl"><div ng-controller="ChildCtrl"></div></div>');
$compile(el)(scope);
parentScope = el.scope();
childScope = el.children().scope();
}));
it('simple false test', function () {
expect(false).toBe(false);
});
});
Try this..
describe('Child controller tests. ', function () {
beforeEach(module('testApp'));
var ChildCtrl, scope;
beforeEach(inject(function ($controller, $rootScope, $compile) {
scope = $rootScope.$new();
var el = angular.element('<div ng-controller="ParentCtrl"><div ng-controller="ChildCtrl"></div></div>');
$compile(el)(scope);
// to access parent controller.
var parentScope = el.scope();
var childScope = el.children().scope();
// now you should be able to access from parent and child scopes.
}));
it('simple false test', function () {
expect(false).toBe(false);
});
});
This will instantiate ParentCtrl first and then extend the scope of it with the ChildCtrl's scope.
In the example that you have given only ChildCtrl is instantiated ParentCtrl is not instantiated.
Related
I am new to testing AngularJs controllers with Karma and Jasmine.
I'm trying to test this controller:
angular.module('app.dashboard.admin', [])
.controller('AdminCtrl', function (locale, $log, $scope, $window, $state) {
$scope.translation = $window.translation()[locale];
$scope.showAdminBoard = false;
$scope.initModel = {
disableProgress: false,
message: $scope.translation['admin_platform_init'],
error: ''
};
$scope.adminPrivileges = {};
$scope.onGetAdminPrivileges = function () {
return $scope.adinPrivileges;
}
Here's my test code:
'use strict';
describe('dashboard.admin module', function () {
beforeEach(function(){
module('app.dashboard.admin');
});
var auth, scope, ctrl, window;
beforeEach(inject(function ($controller, $rootScope, $window) {
auth = Auth;
scope = $rootScope.$new(); //get a childscope
window = {
translation: $window.translation
};
ctrl = $controller("AdminCtrl", {$scope: scope, $window: window});
}));
describe('Admin Controller', function () {
it('should inject controller', function () {
expect(ctrl).toBeDefined();
});
});
});
However, when I try to execute this test code I get this error:
TypeError: undefined is not an object (evaluating '$scope.translation['admin_platform_init']') (line 11)
views/dashboard.admin/admin.js:11:40
[native code]
instantiate#bower_components/angular/angular.js:4786:61
$controller#bower_components/angular/angular.js:10607:39
bower_components/angular-mocks/angular-mocks.js:2249:23
views/dashboard.admin/admin.spec.js:113:27
invoke#bower_components/angular/angular.js:4771:24
WorkFn#bower_components/angular-mocks/angular-mocks.js:3130:26
loaded#http://localhost:9876/context.js:151:17
inject#bower_components/angular-mocks/angular-mocks.js:3097:28
views/dashboard.admin/admin.spec.js:106:22
global code#views/dashboard.admin/admin.spec.js:3:9
Expected undefined to be defined.
views/dashboard.admin/admin.spec.js:118:37
loaded#http://localhost:9876/context.js:151:17
I have tried to mock the $window object and overriding angular's $window object, but I wasn't successful.
I have checked the dependencies in my karma.conf.js file and they're all there.
I have also checked these questions:
Karma-Jasmine: How to test $translate.use?
jasmine mock window object
but the proposed solutions didn't really help.
Thus, I'm trying to find a way to mock the $scope.translation['admin_platform_init'] object in order to be able to execute my tests.
Can someone please point me in the right direction?
Thank you.
try this instead of $window.translation
window = {
translation: function () {
return {
"admin_platform_init": "This is test message"
};
}
};
I managed to solve my problem by importing the 'app' module.
The test code after the fix looks like this:
'use strict';
describe('dashboard.admin module', function () {
beforeEach(function(){
module('app');
module('app.dashboard.admin');
});
var auth, scope, ctrl, window;
beforeEach(inject(function ($controller, $rootScope, $window) {
auth = Auth;
scope = $rootScope.$new(); //get a childscope
window = {
translation: $window.translation
};
ctrl = $controller("AdminCtrl", {$scope: scope, $window: window});
}));
describe('Admin Controller', function () {
it('should inject controller', function () {
expect(ctrl).toBeDefined();
});
});
});
Using Angular and Jasmine I would like to run the service method with some mockup data. Below is the code of my test which uses some working RoomsController trying to run test() method on the RoomsParamsSvc:
describe('Rooms Controller', function() {
var RoomsController,
scope,
location,
httpBackend,
RoomsParamsSvc;
beforeEach(module('rooms', function ($provide, $injector) {
RoomsParamsSvc = function () { //(1a)
return $injector.get('RoomsParamsSvc'); //(1b)
}; //(1c)
$provide.value('RoomsParamsSvc', RoomsParamsSvc); //(1d)
}));
beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, _RoomsParamsSvc_) {
// Set a new global scope
scope = $rootScope.$new();
location = $location;
httpBackend = $httpBackend;
RoomsParamsSvc = _RoomsParamsSvc_;
RoomsController = $controller('RoomsController', {
$scope: scope,
$location: location,
RoomsParamsSvc: RoomsParamsSvc
});
}));
it('should have test as a function', function () {
var t = RoomsParamsSvc.test();
});
});
As far as I understand with the with injector I should be able to use that injected service. Without (1a-1d) I got an error:
Error: [$injector:unpr] Unknown provider: RoomsParamsSvcProvider <-
RoomsParamsSvc
However now it doesn't work, too. I got an error meaning that test() is not a function:
jasmine typeerror 'undefined' is not a function (evaluating 'RoomsParamsSvc.test()')
My service looks like that:
var roomsApp = angular.module('rooms', []);
roomsApp.factory('RoomsParamsSvc', function () {
var factory = {};
factory.test = function ()
{
return '';
}
return factory;
});
Do you have any suggestions?
Lines 1a-1d are not required, as the 'RoomsParamsSvc' is loaded within your 'room' module. But you make a reference to the RoomsController, which is undefined.
beforeEach(module('rooms'));
beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, _RoomsParamsSvc_) {
// Set a new global scope
scope = $rootScope.$new();
location = $location;
httpBackend = $httpBackend;
RoomsParamsSvc = _RoomsParamsSvc_;
RoomsController = $controller(function() {}, {
$scope: scope,
$location: location,
RoomsParamsSvc: RoomsParamsSvc
});
console.log(RoomsParamsSvc);
}));
Plunker
Goal:
Write a passing test for the waCarousel directive scope variable: self.awesomeThings. Expect this test pass when self.awsomeThings.length.toBe(3) to is true?
Question:
How can I properly write this test? rather how do I inject a directives controller?
Directive:
angular.module('carouselApp')
.directive('waCarousel', function() {
return {
templateUrl: '../../../views/carousel/wa.carousel.html',
controller: function($scope) {
var self = this;
self.awesomeThings = [1, 2, 3];
return $scope.carousel = self;
}
}
});
Unit Test:
describe('waCarousel Unit', function() {
// am I missing a $controller & namespace variable init?
var $compile,
$rootScope;
// Load the myApp module, which contains the directive
beforeEach(module('carouselApp'));
// Store references to $rootScope and $compile and $controller
// so they are available to all tests in this describe block
beforeEach(inject(function(_$compile_, _$rootScope_, _$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
// WaCarouselCtrl = $controller('WaCarouselCtrl', {
// $scope: scope
// });
}));
it('should have a list of awesomeThings', function() {
// This wont pass
expect(scope.awesomeThings.length).toBe(3);
});
});
This is how I would do it for a typical view and not directive:
describe('Controller: MainCtrl', function() {
// load the controller's module
beforeEach(module('carouselApp'));
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
// !!*** this is how I would inject the typical controller of a view **!! //
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function() {
expect(scope.awesomeThings.length).toBe(3);
});
});
How do I merge these two concepts so that I can expect self.awesomeThings.length).toBe(3)?
UPDATE:
Compile the element, and after calling $digest(), you will have access to the scope which contains carousel object with awesomeThings array:
describe('waCarousel Unit', function() {
var scope;
beforeEach(module('carouselApp'));
beforeEach(inject(function($rootScope, $compile) {
var element = '<test></test>';
scope = $rootScope.$new();
element = $compile(element)(scope);
scope.$digest();
}));
it('should have a list of awesomeThings', function() {
expect(scope.carousel.awesomeThings.length).toBe(3);
});
});
Also, here are some useful links to testing directives in angular:
Testing Directives
Testing AngularJS directive controllers with Jasmine and Karma
Introduction to Unit Test: Directives
I am trying to write unit tests for my code for the first time. I can test $scopes when declared at the top of my controllers, but how can i test $scopes that are inside a $scope function?
Ctrl:
app.controller("MyCtrl", function($scope, $http, myService) {
$scope.initialiseValue = 0;
$scope.address = function (value) {
$scope.addressValue = 0;
}
});
Test:
describe('app', function () {
var app;
beforeEach(function () {
app = angular.mock.module('app')
});
describe('MyCtrl', function () {
var scope, ctrl, theService , httpMock;
beforeEach(inject(function($controller, $rootScope, myService, $httpBackend) {
scope = $rootScope.$new(),
ctrl = $controller('MyCtrl', {
$scope: scope,
$location : location,
myService: theService ,
$httpBackend: httpMock
});
}));
// This test succeeds ///////////
it('initialiseValue should be initialised to 0', function() {
console.log(scope.initialiseValue );
expect(scope.initialiseValue ).toBe(0);
});
// This test fails ///////////
it('addressValue should be initialised to 0', function() {
console.log(scope.addressValue );
expect(scope.addressValue ).toBe(0);
});
});
Error:
Expected undefined to be false.
Using Karma and Jasmine.
Any help would be appreciated.
You forgot to call the
scope.address()
function inside the test. If you do not call this function,
$scope.addressValue
can not be updated. Updated working fiddle http://jsfiddle.net/themyth92/j3pkaebw/1/
Why am I unable to access my function in my Controller? The code functions like I would expect it too, however, it doesn't seem to want to allow me access to my function that I'm trying to unit test. It should just return a simple bool, but it's getting killed somewhere.
Here's some code:
RTHelper.js
describe('Unit: LocationController', function () {
var $scope, $httpBackend, $location, injector, ctrl, $controller;
//beforeEach(function () {
// angular.module('TDE').controller('LocationController'); //
// inject(function ($injector) {
// $rootScope = $injector.get('$rootScope');
// $scope = $rootScope.$new();
// //ctrl = $injector.get('$controller')("LocationController", { $scope: $scope });
// injector = $injector;
// ctrl = $injector.get('$controller');
// //scope = $injector.get('$rootScope').$new();
// $httpBackend = $injector.get('$httpBackend');
// $location = $injector.get('$location');
// });
//});
//both beforeEach methods work(which one is better? I don't know), so things are getting loaded
beforeEach(function () {
angular.module('TDE');
inject(function ($injector) {
$location = $injector.get('$location');
$rootscope = $injector.get('$rootScope');
$scope = $rootscope.$new();
$controller = $injector.get('$controller');
ctrl = function () {
return $controller('LocationController', {
'$scope': $scope
})
};
})
});
it("should just be a holder for something for later", function () {
expect($scope.BoolCondition()).toBeDefined(); //I don't care what it returns as long as it's accessed honestly
});
})
LocationController.js
angular
.module('TDE')
.controller('LocationController', ['$rootScope', '$scope', '$location', '$window', '$document', 'LocationService', 'HeaderFooterService', 'SearchService', 'TranslationService', 'MTDE_CONFIG', 'LocationPartnerAssignmentService', 'ExperimentService', function ($rootScope, $scope, $location, $window, $document, $LocationService, $HeaderFooterService, $SearchService, $TranslationService, $MTDE_CONFIG, $LocationPartnerAssignmentService, $ExperimentService) {
$scope.BoolCondition = function(myCondition){
if(//blah blah condition test on myCondition)
{
return true
}
else
{
return false;
}
}
How would I go about getting to that BoolCondition? I'm new to this so you can imagine the struggle of writing unit tests after never having done unit testing. I've also gone through countless examples and I've done some generic tests, so I'm not totally un-versed.
You're not bootstrapping the module under test correctly. You should use angular.mock.module inside the test
You're not instantiating the controller (where's the call to ctrl()?)
Here's the complete working example and the fiddle that runs it:
describe('Unit: LocationController', function () {
var $scope, $location, ctrl;
beforeEach(function () {
angular.mock.module('TDE');
inject(function (_$location_, _$rootScope_, _$controller_) {
$location = _$location_;
$scope = _$rootScope_.$new();
ctrl = _$controller_('LocationController', {
'$scope': $scope
})
});
});
it("should just be a holder for something for later", function () {
expect($scope.BoolCondition()).toBeDefined();
expect($scope.BoolCondition()).toBeTruthy();
});
})