angularjs jasmine tests: Variable vm not found - angularjs

New to angular and Jasmine tests in angular. So I am writing a sample unit test to test a length of an array upon initialization in my controller. I expect the array to be of length 0. However, not sure what I am missing, the test output says that it cannot find the variable vm I am using to refer to the array. Here is my test:
(function(){
'use strict';
describe('Testing UploadedReleasesController', function() {
beforeEach(module('app.uploadedReleases'));
describe('Testing uploaded releases controller', function(){
var vm, controller;
beforeEach(inject(function($controller, $rootScope){
vm = $rootScope.$new();
controller = $controller('UploadedReleasesController', {$scope:vm});
}));
});
describe('album length', function(){
it('it should test album length', function () {
expect(vm.albums).toBeDefined();
expect(vm.albums.length).toBe(0);
});
});
});
})();
Output: Testing UploadedReleasesController album length it should test album length FAILED
ReferenceError: Can't find variable: vm
Any suggestions ?
EDIT
After other responses, I modified my .spec file and the error of vm not being able to find goes away. But now I have a different error. Here is the updated code:
(function(){
'use strict';
describe('Testing UploadedReleasesController', function() {
var scope, controller;
beforeEach(inject(function($controller, $rootScope){
scope = $rootScope.$new();
controller = $controller('UploadedReleasesController', {$scope:scope});
}));
beforeEach(module('app.uploadedReleases'));
describe('album length', function(){
it('it should test album length', function () {
//expect(vm.albums).toBeDefined();
expect(scope.albums.length).toBe(0);
});
});
});
})();
Error: `PhantomJS 1.9.8 (Mac OS X 0.0.0) Testing UploadedReleasesController true Should be true FAILED
Error: [ng:areq] Argument 'UploadedReleasesController' is not a function, got undefined
http://errors.angularjs.org/1.3.20/ng/areq?p0=UploadedReleasesController&p1=not%20a%20function%2C%20got%20undefined
undefined
at assertArg (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:1590)
at assertArgFn (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:1601)
at /Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:8493
at /Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular-mocks/angular-mocks.js:1916
at /Users/rgoti/ingestion/external-ingestion/app/public/src/app/uploadedReleases/uploaded-releases.controller.spec.js:10
at invoke (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4219)
at workFn (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular-mocks/angular-mocks.js:2475)
Error: Injector already created, can not register a module!`
ANOTHER EDIT: Added Oscar's solution which makes the previous error go away. Now I have another problem. HEre is my actual controller definition:
(function (){
angular.module('app.uploadedReleases')
.controller('UploadedReleasesController', UploadedReleasesController)
.controller('ModalController', ModalController);
var ACTION = {
CANCEL: 0,
SAVE: 1,
DELETE: 2,
SUBMIT: 3
};
UploadedReleasesController.$inject = ['$log', '$scope', '$modal', 'ReleaseService', 'TrackService', 'APP_CONFIG'];
function UploadedReleasesController ($log, $scope, $modal, releaseService, trackService, APP_CONFIG){
So ModalController is not there in my spec file. Is that why I get the following error ?
`Error: [$injector:unpr] Unknown provider: $modalProvider <- $modal <- UploadedReleasesController
http://errors.angularjs.org/1.3.20/$injector/unpr?p0=%24modalProvider%20%3C-%20%24modal%20%3C-%20UploadedReleasesController
at /Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4031
at getService (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4178)
at /Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4036
at getService (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4178)
at invoke (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4210)
at instantiate (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4227)
at /Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:8524
at /Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular-mocks/angular-mocks.js:1916
at /Users/rgoti/ingestion/external-ingestion/app/public/src/app/uploadedReleases/uploaded-releases.controller.spec.js:12
at invoke (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular/angular.js:4219)
at workFn (/Users/rgoti/ingestion/external-ingestion/app/public/bower_components/angular-mocks/angular-mocks.js:2475)
undefined
Expected undefined to be defined.
at /Users/rgoti/ingestion/external-ingestion/app/public/src/app/uploadedReleases/uploaded-releases.controller.spec.js:22
TypeError: 'undefined' is not an object (evaluating 'vm.albums.length')
at /Users/rgoti/ingestion/external-ingestion/app/public/src/app/uploa`
Is so, how do I resolve it ?

You have to define vm earlier in the outer scope:
(function(){
'use strict';
describe('Testing UploadedReleasesController', function() {
var vm, controller;
beforeEach(module('app.uploadedReleases'));
describe('Testing uploaded releases controller', function(){
beforeEach(inject(function($controller, $rootScope){
vm = $rootScope.$new();
controller = $controller('UploadedReleasesController', {$scope:vm});
}));
});
describe('album length', function(){
it('it should test album length', function () {
expect(vm.albums).toBeDefined();
expect(vm.albums.length).toBe(0);
});
});
});
})();

Well, you're defining vmin the first describe() function, and you're using it in the second one. So it's indeed undefines. Variables are scoped to their enclosing functions.
Also, you shouldn't name a variable of type "Scope" vm. vm is typically used to refer to the current controller, not to its scope. Use... $scope.

Your two inner describe functions are at the same level as one another. The vm variable isn't in scope for the second one.
Either nest the second one inside the first. That's what I think you want. Something like this:
describe('Testing uploaded releases controller', function(){
var vm, controller;
beforeEach(inject(function($controller, $rootScope){
vm = $rootScope.$new();
controller = $controller('UploadedReleasesController', {$scope:vm});
}));
describe('album length', function(){
it('it should test album length', function () {
expect(vm.albums).toBeDefined();
expect(vm.albums.length).toBe(0);
});
});
});
Or pull the vm variable declaration outside of the first ("'Testing uploaded...") into the top level describe.

Move the beforeEach(inject.... directly inside describe('Testing UploadedReleasesController' and instantiate the variables: var vm, controller;
describe('Testing UploadedReleasesController', function() {
var vm, controller;
beforeEach(inject(....
The before each in the 'root' suite (Testing UploadedReleasesController) will be run for each test in the the inner suites. The inner suites will also have the vm, and controller variables availalbe. Additionally, I would create an afterEach function that resets the variables to undefined.
Update:
Move the 'album length' suite inside 'Testing uploaded releases controller':
(function(){
'use strict';
describe('Testing UploadedReleasesController', function() {
beforeEach(module('app.uploadedReleases'));
describe('Testing uploaded releases controller', function(){
var vm, controller;
beforeEach(inject(function($controller, $rootScope){
vm = $rootScope.$new();
controller = $controller('UploadedReleasesController', {$scope:vm});
}));
afterEach(function() {
vm = undefined;
controller = undefined;
});
describe('album length', function(){
it('it should test album length', function () {
expect(vm.albums).toBeDefined();
expect(vm.albums.length).toBe(0);
});
});
});
});
})();

Related

Cant access Model (function) in Directive while unit testing

Unit testing a Directive which uses ngModel that has several function including getAll(). Model gets injected perfectly (when I output it, it shows the accessible getters/setters/etc). I pass it to the element. Do a compile and digest.
Getting the error 'TypeError: Cannot read property 'getAll' of undefined' though.
'console.log('vehiclesModel', vehiclesModel.get('vehicles'));'
Outputs the stubbedData!
'use strict';
describe('Directive: selectBox', function () {
beforeEach(module('sytacApp'));
beforeEach(module('syt.templates'));
var scope,
httpBackend,
$rootScope,
$compile,
element,
vehiclesModel,
stubbedData;
beforeEach(function () {
inject(function ($injector) {
$compile = $injector.get('$compile');
});
});
beforeEach(inject(function (_$rootScope_, _$httpBackend_, _vehiclesModel_, _stubbedData_) {
httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
vehiclesModel = _vehiclesModel_;
stubbedData = _stubbedData_;
vehiclesModel.set('vehicles', {data: stubbedData.container});
console.log('vehiclesModel', vehiclesModel.get('vehicles'));
}));
it('should process model data accordingly', function () {
var element = angular.element('<select-box identifier="type" selectedidentifier="selectedType" model="vehiclesTypesModel" data-ng-model="vehiclesModel"></select-box>');
element = $compile(element)(scope);
scope.$digest();
//......
});
});
Question. Am I overlooking something?
had to put ´vehiclesModel´ on ´scope scope.vehiclesModel´ before ´$compile´

angular directive with dependencies testing

I am trying to be a good developer & write some tests to cover a directive I have. The directive has a service injected in which makes a call to a webApi endpoint.
When I run the test (which at minute expects 1 to equal 2 so I can prove test is actually running!!) I get an error that an unexpected request GET has been made to my real endpoint even though I thought I had mocked/stubbed out the service so test would execute. My test looks something like the below:
I thought that by calling $provide.service with the name of my service and then mocking the method "getUserHoldings" then this would automatically be injected at test time, have I missed a trick here? The path of the endpoint the unexpected request is contained in the actual getUserHoldings method on the concrete service.
Thanks for any help offered as driving me potty!!!
describe('directive: spPlanResults', function () {
var scope;
var directiveBeingTested = '<sp-plan-results></sp-plan-results>';
var element;
beforeEach (module('pages.plans'));
beforeEach (inject(function ($rootScope,
$compile,
currencyFormatService,
_,
moment,
plansModel,
appConfig,
$timeout,
$q,
$provide) {
scope = $rootScope.$new();
$provide.service('plansService', function () {
return {
getUserHoldings: function() {
var deferred = $q.defer();
return deferred.resolve([
{
class: 'Class1',
classId: 2,
award: 'Award1',
awardId : 2
}]);
}
};
});
element = $compile(directiveBeingTested)(scope);
scope.$digest();
});
it ('should be there', inject(function() {
expect(1).equals(2);
}));
});
Referencing - http://www.mikeobrien.net/blog/overriding-dependencies-in-angular-tests/ - it would work if you did your '$provide' configuration in the 'module's context i.e. do something like -
describe('directive: spPlanResults', function () {
var scope;
var directiveBeingTested = '<sp-plan-results></sp-plan-results>';
var element;
beforeEach(module('pages.plans', function($provide) {
$provide.value('plansService', function() {
return {
getUserHoldings: function() {
var deferred = $q.defer();
return deferred.resolve([{
class: 'Class1',
classId: 2,
award: 'Award1',
awardId: 2
}]);
}
};
});
}));
beforeEach(inject(function($rootScope, $compile, currencyFormatService, _, moment, plansModel, appConfig, $timeout, $q) {
scope = $rootScope.$new();
element = $compile(directiveBeingTested)(scope);
scope.$digest();
});
it('should be there', inject(function() {
expect(1).equals(2);
})); });

Angular JS Unit Testing (Karma Jasmine)

This is my service
angular.module('providers',)
.provider('sample', function(){
this.getName = function(){
return 'name';
};
this.$get = function($http, $log, $q, $localStorage, $sessionStorage) {
this.getTest = function(){
return 'test';
};
};
});
This is my unit test
describe('ProvideTest', function()
{
beforeEach(module("providers"));
beforeEach(function(){
module(function(sampleProvider){
sampleProviderObj=sampleProvider;
});
});
beforeEach(inject());
it('Should call Name', function()
{
expect(sampleProviderObj.getName()).toBe('name');
});
it('Should call test', function()
{
expect(sampleProviderObj.getTest()).toBe('test');
});
});
I am getting an error Type Error: 'undefined' is not a function evaluating sampleProviderObj.getTest()
I need a way to access function inside this.$get . Please help
You should inject your service into the test. Replace this:
beforeEach(function(){
module(function(sampleProvider){
sampleProviderObj=sampleProvider;
});
});
beforeEach(inject());
With this:
beforeEach(inject(function(_sampleProvider_) {
sampleProvider = _sampleProvider_;
}));
Firstly, you need, as had already been said, inject service, that you test. Like following
beforeEach(angular.mock.inject(function ($injector) {
sampleProviderObj = $injector.get('sample');
}));
Second, and more important thing. Sample have no any getTest functions. If you really need to test this function, you should as "Arrange" part of your test execute also $get function of your provider. And then test getTest function of result of previous execution. Like this:
it('Should call test', function()
{
var nestedObj = sampleProviderObj.$get(/*provide correct parameters for this function*/)
expect(nestedObj.getTest()).toBe('test');
});
But it's not good because this test can fail even if nestedObj.getTest work properly (in case when sampleProviderObj.$get works incorrect).
And one more thing, it seems like you need to inject this services $http, $log, $q, $localStorage, $sessionStorage to you provider rather then passing them as parameters.

AngularJS Jasmine error 'Controller is not a function' when instantiated with arguments

I have been doing angularJS for a while now (without tests) but I want to do it properly! I have a controller defined like so
(function () {
'use strict';
angular.module('app')
.controller('CarehomeListCtrl', ['$scope', 'carehomesDataService', carehomeListCtrl]);
function carehomeListCtrl($scope, carehomesDataService) {
var vm = this;
vm.carehomeCollection = [];
vm.activate = activate;
function activate() {
vm.carehomeCollection = carehomesDataService.getAllCarehomes();
}
activate();
}
})();
and then my spec
describe("Carehomes tests", function () {
var $scopeConstructor, $controllerConstructor;
beforeEach(module('app'));
beforeEach(inject(function ($controller, $rootScope) {
$controllerConstructor = $controller;
$scopeConstructor = $rootScope;
}));
describe("CarehomeListCtrl", function () {
var ctrl, dataService, scope;
function createController() {
return $controllerConstructor('CarehomeListCtrl', {
$scope: scope,
carehomesDataService: dataService
});
}
beforeEach(inject(function ($injector) {
scope = $scopeConstructor.$new();
dataService =$injector.get('carehomesDataService') ;
}));
it("should have a carehomesCollection array", function () {
ctrl = createController();
expect(ctrl.carehomesCollection).not.toBeNull();
});
it("should have 3 items in carehomesCollection array when load is called", function () {
ctrl = createController();
expect(ctrl.carehomeCollection.length).toBe(3);
});
});
});
The problem here is that the call to instantiate my controller fails with error whenever I call it with any arguments whether an empty object {} or just $scope : scope} so I know the problem is not carehomesDataService.
Result StackTrace: Error: [ng:areq] Argument 'CarehomeListCtrl' is not
a function, got undefined
http://errors.angularjs.org/1.2.26/ng/areq?p0=CarehomeListCtrl&p1=not%20a%20function%2C%20got%20undefined
However, if I instantiate that controller like this $controllerConstructor('CarehomeListCtrl'); without arguments, it gets instantiated. I'm stumped!
carehomesDataService is a custom service I have written but it's own tests pass and it is correctly injected into the controller in the application.
Any help would be massively appreciated.
Note: I do not quite agree with defining properties on the controller as the view model instead of on $scope but I am following Jesse Liberty's pluralsight course and that's how he does it....plus injecting scope isn't quite working right now which is annoying. Thanks in advance.

jasmine angularjs testing - Argument 'PhoneListCtrl' is not a function, got undefined

When running an angularjs + Jasmine + Karma test, I got following error:
My test script is:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', { $scope: scope });
expect(scope.phones.length).toBe(3);
}));
});
});
This code is just a copy from official AngularJS tutorial here:
http://code.angularjs.org/1.2.0-rc.3/docs/tutorial/step_02
Here is part of my karma.conf.js file:
// list of files / patterns to load in the browser
files: [
'js/bower_components/angular/angular.js',
'js/bower_components/angular/ngular-mocks.js',
'js/app/controllers.js',
'test/unit/*.js'
],
The error is PhoneListCtrl not define, but I beleive it is defined and loaded in the above code. What do you think is the problem? Thanks!
Module initialization part is missing in your unit test. You should call module('phonecatApp') before you first time call inject(). Your unit test code in this case should look like:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
beforeEach(function() {
module('phonecatApp'); // <= initialize module that should be tested
});
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', { $scope: scope });
expect(scope.phones.length).toBe(3);
}));
});
});
where phonecatApp is the name of the module where you defined your PhoneListCtrl controller.
Also tutorial you are using is outdated, it is for unstable version of Angular (1.2.0-rc.3). Here is an updated version of the same tutorial for the latest version of Angular: http://docs.angularjs.org/tutorial/step_02
this works for me
describe('addCatControllerTest', function() {
describe('addCatController', function(){
beforeEach(function() {
module('app');
});
beforeEach(inject(function($controller, $rootScope){
$scope = $rootScope.$new();
}));
it('Add Cat Controller test', inject(function($controller) {
var scope = {},
ctrl = $controller('addCatController', { $scope: scope });
expect(scope.title).toBe('Add Cat');
}));
});
});

Resources