Why won't $scope be found in my AngularJS / Karma unit test? - angularjs

My controller:
angularMoonApp.controller('SourceController', ['$scope', '$rootScope', '$routeParams', 'fileService', function ($scope, $rootScope, $routeParams, fileService) {
$scope.init = function() {
$rootScope.currentItem = 'source';
fileService.getContents($routeParams.path).then(function(response) {
$scope.contents = response.data;
$scope.fileContents = null;
if(_.isArray($scope.contents)) {
// We have a listing of files
$scope.breadcrumbPath = response.data[0].path.split('/');
} else {
// We have one file
$scope.breadcrumbPath = response.data.path.split('/');
$scope.breadcrumbPath.push('');
$scope.fileContents = atob(response.data.content);
fileService.getCommits(response.data.path).then(function(response) {
$scope.commits = response.data;
});
}
});
}
$scope.init();
}]);
My unit test:
(function() {
describe('SourceController', function() {
var $scope, $rootScope, $httpBackend, $routeParams, $q, createController, fileService, deferred;
beforeEach(module('angularMoon'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$routeParams = $injector.get('$routeParams');
$scope = $rootScope.$new();
$q = $injector.get('$q');
deferred = $q.defer();
fileService = $injector.get('fileService');
var $controller = $injector.get('$controller');
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
};
}));
it("should set the current menu item to 'source'", function() {
createController();
$scope.init();
expect($rootScope.currentItem).toBe('source');
});
it("should get test the getContents call of the fileService", function() {
spyOn(fileService, 'getContents').andCallThrough();
createController();
$scope.init();
expect(fileService.getContents).toHaveBeenCalled();
});
it("should return an object with multiple files", function() {
var multipleFiles = [{path: '.DS_Store'}, {path: '.bowerrc'}];
deferred.resolve(multipleFiles);
spyOn(fileService, 'getContents').andReturn(deferred.promise);
createController();
$scope.init();
expect($scope.contents).toBe(multipleFiles);
expect($scope.breadcrumbPath).toBe('');
});
});
})();
The last test fails with:
Expected undefined to be [ { path : '.DS_Store' }, { path : '.bowerrc' } ].
Expected undefined to be ''.
Why is the $scope undefined here?

Your controller is expecting you to inject in $rootScope which you are not doing in your unit test.
You have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$routeParams': $routeParams,
'fileService': fileService
});
But but should have:
createController = function() {
return $controller('SourceController', {
'$scope': $scope,
'$rootScope': $rootScope,
'$routeParams': $routeParams,
'fileService': fileService
});
Also, you will want to call this code:
createController();
$scope.init();
before you resolve your promise:
deferred.resolve(multipleFiles);

The scope is not undefined. What is undefined is $scope.contents and $scope.breadcrumbPath.
And that's because promise callbacks are always being called asynchronously. You need to call
$scope.$apply()
before verifying your expectations.

Related

unable to access the $scope and $rootscope inside the success of http call

Using Jasmine and chutzpah unable to access the $scope and $rootscope inside the success of http call.
In the jasmine code the var test = $rootScope.langCode is coming as 'undefined'. Which is inside the success callback of $http from factory.
Following are the controller code need to be tested.
app.controller('catalogCtrl', ['$scope', '$rootScope', '$window', 'catalogService', '$routeParams', '$location', '$timeout', '$filter', function ($scope, $rootScope, $window, catalogService, $routeParams, $location, $timeout, $filter) {
$scope.init = function (callback, params) {
catalogService.labeldata().then(function successCallback(response) {
$rootScope.langCode = "test";
}, function errorCallback(response) {
console.log(JSON.parse(JSON.stringify(response)));
$rootScope.langCode = "test1";
});
};
}]);
Following is the Factory where am doing the http call
app.factory('catalogService', ['$http', function ($http) {
return {
labeldata: function () {
return $http({
method: 'GET',
url: "/Content/Index/"
});
}
}
}]);
Jasmine code to test catalogCtrl
describe('catalogCtrl', function () {
var httpBackend, $rootScope, $scope, createController, authRequestHandler, myservice, $controller, $q;
beforeEach(module('catalogModule'));
beforeEach(function () {
module('catalogModule');
inject(function (_$controller_, _$rootScope_, $injector, _$q_) {
// inject removes the underscores and finds the $controller Provider
$controller = _$controller_;
$rootScope = _$rootScope_;
$scope = _$rootScope_.$new();
// Injecting Service references and HttpBackend Object :-
httpBackend = $injector.get('$httpBackend');
$q = _$q_;
myserv = $injector.get('catalogService');
});
});
it('Catalog Content', function () {
var $scope = {}; var $rtScope = {};
// $controller takes an object containing a reference to the $scope
var controller = $controller('catalogCtrl', { $scope: $scope, $rtScope: $rootScope });
// the assertion checks the expected result
var obj = { "COUNTRYCODE": "au", "KEY": "MERCHANDISE", "LANGCODE": "en", "VALUE": "Merchandise" };
var arr = [obj];
var returnData = {};
returnData.data = arr;
//returnData = arr;
//httpBackend.expectGET("/Content/Index/").respond(returnData);
httpBackend.when('GET', "/Content/Index/").respond(returnData);
var returnedData;
myserv.labeldata().then(function (returnData) {
// check that returned result contains
returnedData = result;
expect(returnedData).toEqual({ bar: 'foo' });
});
$scope.init();
var test = $rootScope.langCode;
});
});

Promise not resolving in Jasmine - AngularJS

I'm trying to test a method that deletes an item from a list after user confirmation.
Controller:
app.controller('mainCtrl', ['$scope', '$window', 'dataService', function($scope, $window, dataService) {
var vm = this;
vm.delete = function(id, index) {
if($window.confirm('Are you sure?')) {
dataService.deleteById(id).then(function() {
vm.list.splice(index, 1)
});
}
};
}]);
Sevice:
app.service('dataService', ['$http', function($http) {
this.deleteById = function(id) {
return $http.delete('delete-item?id=' + id);
};
}]);
Test:
describe('Testing RecipesController', function() {
var scope, ctrl, dataServiceMock, q, deferred, window;
beforeEach(function() {
dataServiceMock = {
deleteById: function() {
deferred = q.defer();
return deferred.promise;
}
};
});
beforeEach(function() {
module('app');
inject(function($rootScope, $controller, $q, $window) {
q = $q;
window = $window;
scope = $rootScope.$new();
ctrl = $controller('mainCtrl', {
$scope: scope,
dataService: dataServiceMock
});
});
});
it('should delete recipe if the user clicked "OK"', function() {
spyOn(window, 'confirm').and.returnValue(true);
spyOn(dataServiceMock, 'deleteById').and.callThrough();
var item= {
id: 2,
name: 'Shirt'
};
ctrl.list = ['Hat', 'Shirt'];
ctrl.delete(item, 1);
expect(dataServiceMock.deleteById).toHaveBeenCalled();
expect(ctrl.list.length).toBe(1);
});
});
I successfully mocked the confirm dialog and the delete method, and the test to check if the method been called even passes.
But, The promise.then() isn't working.
After I run the test I got this message "Expected 2 to be 1".
I see one thing for sure, which is that you never resolve or reject your promise in the data service mock. Try changing the mock to this:
beforeEach(function() {
dataServiceMock = {
deleteById: function() {
deferred = q.defer();
deferred.resolve({ /* whatever data you want to resolve with */ });
return deferred.promise;
// You could also shorten this whole mock function to just:
// return $q.resolve({ /* some data */ });
}
};
});
Also, don't forget to execute the $digest() function on the $rootScope at the end of your test... you're actually executing it on your controller's scope, NOT the root scope.
Hold onto the actual $rootScope object - change your beforeEach to:
var $rScope;
beforeEach(function() {
module('app');
inject(function($rootScope, $controller, $q, $window) {
q = $q;
window = $window;
$rScope = $rootScope;
ctrl = $controller('mainCtrl', {
$scope: $rootScope.$new(),
dataService: dataServiceMock
});
});
});
Then in your test, execute $digest on the root scope at the end:
it('should delete recipe if the user clicked "OK"', function() {
// all your test codez...
$rScope.$digest();
});

angularjs with jasmine- TypeError: $controller is not a function

Here is my controller:
angular.module('someapp', [])
.controller('TestCtrl', function($scope, dataservice, $stateParams) {
$scope.messageid = parseInt($stateParams.messageid);
});
spec file:
describe('TestCtrl', function() {
beforeEach(module('someapp'));
var $controller;
$stateParameters = { messageid: 100 };
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
it('should set value of messageid', function() {
var controller = $controller('TestCtrl', {
$stateParams: $stateParameters
});
expect($scope.messageid).toEqual($stateParameters.messageid);
});
});
But it's giving error: TypeError: $controller is not a function
can anyone help me to resolve this?
angular.module('someapp', [])
.controller('TestCtrl', ['$scope', '$stateParams', function($scope, $stateParams) {
$scope.messageid = parseInt($stateParams.messageid);
}]);
describe('TestCtrl', function() {
beforeEach(module('someapp'));
var $controller;
$stateParameters = { messageid: 100 };
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
it('should set value of messageid', function() {
var controller = $controller('TestCtrl', {
$stateParams: $stateParameters;
});
expect($scope.messageid).toEqual($stateParameters.messageid);
});
});
describe('TestCtrl', function() {
var $scope, dataservice;
beforeEach(module('someapp'));
module(function ($provide) {
// Mock dataservice
$provide.service('dataservice', function () {
// Assuming you have a getData function in dataservice
this.getData = jasmine.createSpy('getData');
});
});
//var $controller;
var $stateParameters = { messageid: 100 };
beforeEach(inject(function(_$controller_, _$rootScope_, _dataservice_){
$scope = _$rootScope_.$new();
dataservice = _dataservice_
$controller('TestCtrl', {
$scope : $scope,
dataservice: dataservice,
$stateParams: $stateParameters
});
}));
it('should set value of messageid', function() {
expect($scope.messageid).toEqual($stateParameters.messageid);
});
});

Why won't my karma test work?

I have this simple test:
describe('My Controller', function() {
beforeEach(function() {
module('myApp');
return inject(function($injector) {
var $controller = $injector.get('$controller');
this.rootScope = $injector.get('$rootScope');
this.scope = this.rootScope.$new();
this.controller = $controller('MyCtrl', {
'$scope': this.scope,
});
});
});
it('should have a controller', function() {
expect(this.controller).toBeDefined();
});
});
the controller looks like this:
angular.module('myApp').controller('MyCtrl', ['$scope', '$state', '$filter', '$q', 'BookingService', 'ngToast', '$uibModal',
function($scope, $state, $filter, $q, BookingService, ngToast, $uibModal) {
$scope.bs = BookingService;
$scope.roundTrip = false;
$scope.reservationDetails = {};
$scope.originAddress = false;
$scope.destinationAddress = false;
$scope.reservationDetails.roundTrip = false;
$scope.seatReservationDepart = {};
$scope.charter = false;
}]);
The test keeps failing and the terminal is not really giving any useful information as to why.
Don't use this in your test, it refers to different things in different parts of your suite.
Instead, initialize a scope "container" in the describe namespace:
describe('My Controller', function() {
var scope = {};
beforeEach(function() {
module('myApp');
return inject(function($injector) {
var $controller = $injector.get('$controller');
this.rootScope = $injector.get('$rootScope');
this.scope = this.rootScope.$new();
scope.controller = $controller('MyCtrl', {
'$scope': this.scope,
});
});
});
it('should have a controller', function() {
expect(scope.controller).toBeDefined();
});
});

AngularJS - Unit test for http get to JSON file

I am trying to write a unit test to test a simple factory that performs a http.get to retrieve a JSON file.
The factory is called within my controller.
Here's a plunker showing my http.get: http://plnkr.co/edit/xg9T5H1Kreo4lwxzRQem?p=preview
Ctrl:
app.controller('MainCtrl', function($scope, $http, factoryGetJSONFile) {
factoryGetJSONFile.getMyData(function(data) {
$scope.Addresses = data.Addresses.AddressList;
$scope.People = data.Names.People;
});
});
Factory:
app.factory('factoryGetJSONFile', function($http) {
return {
getMyData: function(done) {
$http.get('data.json')
.success(function(data) {
done(data);
})
.error(function(error) {
alert('An error occured whilst trying to retrieve your data');
});
}
}
});
Test:
// ---SPECS-------------------------
describe('with httpBackend', function () {
var app;
beforeEach(function () {
app = angular.mock.module('plunker')
});
describe('MyCtrl', function () {
var scope, ctrl, theService, httpMock;
beforeEach(inject(function ($controller, $rootScope, factoryGetJSONFile, $httpBackend) {
scope = $rootScope.$new(),
ctrl = $controller('MyCtrl', {
$scope: scope,
factoryGetJSONFile: theService,
$httpBackend: httpMock
});
}));
it("should make a GET call to data.json", function () {
console.log("********** SERVICE ***********");
httpMock.expectGET("data.json").respond("Response found!");
//expect(factoryGetJSONFile.getMyData()).toBeDefined();
httpMock.flush();
});
})
});
Error:
TypeError: 'undefined' is not an object (evaluating 'httpMock.expectGET')
You should assign $httpBackend to httpMock in beforeEach like this:
beforeEach(inject(function ($controller, $rootScope, factoryGetJSONFile, $httpBackend) {
httpMock = $httpBackend;
scope = $rootScope.$new();
ctrl = $controller('MyCtrl', {
$scope: scope,
factoryGetJSONFile: factoryGetJSONFile,
$httpBackend: httpMock
});
}));

Resources