AngularJS - Unit test for http get to JSON file - angularjs

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
});
}));

Related

angularjs unit testing (cannot find propery of 'resolve' undefined angularjs testing)

I am trying to do unit test of my angular app with karma. I am getting some error. Am i missing something? A
This my controller
(function () {
"use strict"
angular
.module("myApp")
.controller("userCtrl",['$scope', '$state', 'userService', 'appSettings','md5','currentUser','$rootScope',
function ($scope, $state, userService, appSettings,md5,currentUser, $rootScope) {
$scope.login = function() {
$scope.loading = true;
if($scope.password != null){
var user ={
username:$scope.username,
password:md5.createHash($scope.password)
}
var getData = userService.login(user);
getData.then(function (response) {
console.log(response);
$scope.loading = false;
currentUser.setProfile(user.username, response.data.sessionId);
$state.go('videos');
}, function (response) {
console.log(response.data);
});
}else{
$scope.msg = "Password field is empty!"
}
}
}])
}());
This is my test codes
'use strict';
describe('userCtrl', function() {
beforeEach(module('myApp'));
var scope, userCtrl, apiService,q, deferred, currentUser;
describe('$scope.login', function(){
beforeEach(function(){
apiService = {
login: function () {
deferred = q.defer();
return deferred.promise;
};
};
});
beforeEach(inject(function($controller, $rootScope, $q, _currentUser_){
var user ={name:'ali',password:'password'};
scope = $rootScope.$new();
q = $q;
// The injector unwraps the underscores (_) from around the parameter names when matching
userCtrl = $controller('userCtrl', {
$scope:scope,
userService:apiService
});
//userService = _userService_;
currentUser = _currentUser_;
}));
it('should call user service login', function() {
spyOn(apiService, 'login').and.callThrough();
scope.login();
deferred.resolve(user);
expect(apiService.login).toHaveBeenCalled();
});
it('checks the password field', function() {
scope.login();
expect(scope.msg).toEqual('Password field is empty!');
});
});
});
And i am getting this error
enter image description here
If you have to test controller then use to spyon for service method and in case of service then use HttpBackend
describe('Testing a Controller that uses a Promise', function() {
var $scope;
var $q;
var deferred;
beforeEach(module('search'));
beforeEach(inject(function($controller, _$rootScope_, _$q_, searchService) {
$q = _$q_;
$scope = _$rootScope_.$new();
// We use the $q service to create a mock instance of defer
deferred = _$q_.defer();
// Use a Jasmine Spy to return the deferred promise
spyOn(searchService, 'search').and.returnValue(deferred.promise);
// Init the controller, passing our spy service instance
$controller('SearchController', {
$scope: $scope,
searchService: searchService
});
}));
it('should resolve promise', function() {
// Setup the data we wish to return for the .then function in the controller
deferred.resolve([{
id: 1
}, {
id: 2
}]);
// We have to call apply for this to work
$scope.$apply();
// Since we called apply, not we can perform our assertions
expect($scope.results).not.toBe(undefined);
expect($scope.error).toBe(undefined);
});
});
This for same using spyon for service method then use $appy method to make it work.

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();
});

Angular $scope method testing

I'm trying to test if a $scope method has been called
my controller :
(function () {
var app = angular.module('productsController',[]);
app.controller('AddProductController', ['$scope',function ($scope) {
$scope.test = function(){
return true;
};
$scope.test();
}]);
})();
and my test :
describe("Products Controller", function () {
beforeEach(function () {
module('productsController');
});
beforeEach(function () {
var createController, scope, rootScope;
});
describe('AddProductController', function () {
beforeEach(function () {
inject(function ($controller, _$rootScope_) {
rootScope = _$rootScope_;
scope = _$rootScope_.$new();
createController = function () {
return $controller("AddProductController", {
$scope: scope,
});
};
});
});
it('should call test', function(){
spyOn(scope, 'test');
createController();
scope.$apply()
expect(scope.test).toHaveBeenCalled();
})
});
});
but I got this error:
Error: test() method does not exist
$scope.test exists any when I run the app in a browser the method is been called, but if falis whne testing, any clues?
May be there is some problem in the ordering, Could you try and check if the below works?
describe('AddProductController', function() {
var createController, scope, rootScope;
beforeEach(function() {
inject(function($controller, _$rootScope_) {
rootScope = _$rootScope_;
scope = _$rootScope_.$new();
createController = function() {
return $controller("AddProductController", {
$scope: scope,
});
};
});
});
it('should call test', function() {
createController(); // To create function test in the scope
spyOn(scope, 'test');
createController(); // To execute test after spy is added.
expect(scope.test).toHaveBeenCalled();
})
});

Angular - mocking out service method that returns a promise

I am trying to test a controller method that relies on a service call to get some data. The service method returns a promise, and I'd like to test the behavior of the controller if the promise is resolved or rejected.
I have come up with this approach to vary the behavior of my mocked service method, but it does not work. The getDataSuccess flag is always true when the mocked getData method is called. Here's what I have so far:
Controller:
app.controller('myController', function($scope, myService) {
myService.getData()
.then(function (data) {
$scope.data = data;
},
function (data) {
$scope.serverError = data;
});
});
Test:
describe('myController', function () {
var ctl, serviceMock, getDataSuccess, scope;
beforeEach(function() {
getDataSuccess = true;
serviceMock = {};
module('app', function ($provide) {
$provide.value('myService', serviceMock);
});
inject(function ($q) {
serviceMock.getData = function () {
var defer = $q.defer();
if (getDataSuccess) {
defer.resolve("theData");
} else {
defer.reject("theData");
}
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, $httpBackend, myService) {
scope = $rootScope.$new();
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
}));
describe('myController loading data', function () {
it('should set $scope.data if data load succeeds', function () {
getDataSuccess = true;
scope.$apply();
expect(scope.data).toEqual("theData");
});
it('should set $scope.serverError if data load fails', function () {
getDataSuccess = false;
scope.$apply();
expect(scope.serverError).toEqual("theData");
});
});
});
Clearly I'm missing something here. The order of execution is not what I was expecting. What's the proper way to do this sort of thing?
Here's this example in Plunker: http://plnkr.co/edit/ODyslivLorjaLM4EqlEF?p=preview
myService.getData function is called where myController is initialized. So if you want to change the behavior getData function by setting getDataSuccess, you need to initialize myController after you set getDataSuccess true/false.
What I recommend is something like this.
In appSpec.js
describe('myController', function () {
var ctl, serviceMock, getDataSuccess, scope;
beforeEach(function() {
getDataSuccess = true;
serviceMock = {};
module('app', function ($provide) {
$provide.value('myService', serviceMock);
});
inject(function ($q) {
serviceMock.getData = function () {
var defer = $q.defer();
if (getDataSuccess) {
defer.resolve("theData");
} else {
defer.reject("theData");
}
return defer.promise;
};
});
});
beforeEach(inject(function ($rootScope, $controller, $httpBackend, myService) {
scope = $rootScope.$new();
//
// ctl = $controller('myController', {
// $scope: scope,
// myService: myService,
// });
}));
describe('myController loading data', function () {
it('should set $scope.data if data load succeeds', inject(function($controller, myService){
getDataSuccess = true;
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
scope.$apply();
expect(scope.data).toEqual("theData");
}));
it('should set $scope.serverError if data load fails', inject(function($controller, myService){
getDataSuccess = false;
ctl = $controller('myController', {
$scope: scope,
myService: myService,
});
scope.$apply();
expect(scope.serverError).toEqual("theData");
}));
});
});
This is updated plunker.

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

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.

Resources