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/
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();
});
});
});
I've begun using jasmine to test my controllers in angularjs however after reading some tutorials I am a bit stuck.
I have this simple angularjs controller called jasmineController
(function () {
"use strict";
var myAppModule = angular.module('myApp');
myAppModule.controller('jasmineController', ['$scope', 'genericService',
function ($scope, genericService) {
$scope.name = 'Superhero';
$scope.counter = 0;
$scope.$watch('name', function (newValue, oldValue) {
$scope.counter = $scope.counter + 1;
});
$scope.testPromise = function() {
return genericService.getAll("dashboard", "currentnews", null, null, null);
}
$scope.getNewsItems = function () {
genericService.getAll("dashboard", "currentnews", null, null, null).then(function (data) {
$scope.name = 'Superhero';
$scope.newsItems = data;
});
}
}
]);
})();
In my jasmine test I wanted to call getNewsItems and check that it can call genericService.getAll and that $scope.newsItems is assigned some data. I understand that I would be mocking out the service and I won't actually call it.
Here is my spec
describe("test", function () {
// Declare some variables required for my test
var controller, scope, genericService;
// load in module
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, _genericService_) {
genericService = _genericService_;
// assign new scope to variable
scope = $rootScope.$new();
controller = $controller('jasmineController', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(scope.name).toBe('Superhero');
});
it('should assign data to scope', function() {
//var fakeHttpPromise = {success: function () { }};
scope.getNewsItems();
spyOn(genericService, 'getAll');
expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews');
});
});
I've got a spyon for genericService.getall() but apart from that I am a bit stuck with checking that my scope variable is assigned a value.
I also get this stack trace:
Error: Expected spy getAll to have been called with [ 'dashboard', 'currentnews' ] but it was never called.
at stack (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1441:11)
at buildExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1408:5)
at expectationResultFactory (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:533:11)
at Spec.prototype.addExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:293:5)
at addExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:477:9)
at Anonymous function (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1365:7)
at Anonymous function (file:///C:/Projects/2013/AMT2015/AMT2015.WebAPP/Scripts/tests/controllers/dashboardControllerSpec.js:49:9)
at attemptSync (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1759:9)
at QueueRunner.prototype.run (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1747:9)
at QueueRunner.prototype.execute (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1733:5)
You need to put the spy first before calling the test function. And you are you actually passing more parameters to the service function. So you need to test with the exact parameter list.
it('should assign data to scope', function() {
//var fakeHttpPromise = {success: function () { }};
spyOn(genericService, 'getAll');
scope.getNewsItems();
expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews',null,null,null);
});
I ended up doing this:
describe("test", function () {
// Declare some variables required for my test
var controller, scope, genericService;
// load in module
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, _$q_, _genericService_) {
genericService = _genericService_;
var deferred = _$q_.defer();
deferred.resolve('resolveData');
spyOn(genericService, 'getAll').and.returnValue(deferred.promise);
scope = $rootScope.$new();
controller = $controller('jasmineController', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(scope.name).toBe('Superhero');
});
it('should assign data to scope', function() {
//spyOn(genericService, 'getAll').and.callFake(function() {
//});
scope.getNewsItems();
scope.$apply();
expect(scope.newsItems).toBe('resolveData');
//expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews', null, null, null);
});
});
Because my test is more than just calling a service but handling a promise as well I had to inject $q. Then with spy on I say to call service and method and the return value is the deferred promise.
Finally I can look at the scope variable to see if anything is assigned with this line:
expect(scope.newsItems).toBe('resolveData');
Thanks to everyone that helped.
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
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.
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();
});
})