Testing an http request where in success call another function with jasmine - angularjs

I have a controller with an http request. On success it calls another function and I do not know how to test it. I am new to angular.
var app = angular.module('myApp', ['']);
app.controller('MainController', ['$scope', '$http', function ($scope, $http) {
$scope.source = '';
$scope.destination = '';
var selectedFiles = [];
$scope.deleteFiles = function(source) {
if (source == $scope.source) {
selectedFiles = selectedFilesSource;
} else if (source == $scope.destination) {
selectedFiles = selectedFilesDestination;
}
$http({
method: 'POST',
url: 'deleteFiles.php',
data:
{
"sourcePath": source,
"selectedFiles": selectedFiles
}
}).success(function(data) {
if (source == $scope.source) {
$scope.showFiles(source, 'source');
} else if (source == $scope.destination) {
$scope.showFiles(source, 'destination');
}
});
};
My testing file is like that:
describe("Testing to MainController", function(){
beforeEach(module('myApp'));
var mainController, scope, httpBackend, http;
beforeEach(inject(function($controller, $rootScope, $httpBackend, $http) {
scope = $rootScope;
httpBackend = $httpBackend;
http = $http;
httpBackend.when('POST', 'deleteFiles.php', function(data){return{"sourcePath": "source", "selectedFiles": ''}})
.respond(?????);
mainController = $controller('MainController', {
$scope: scope,
$http: http
});
}));
it('should call showFiles if sourcepath is source', function() {
scope.source = 'files/set1';
scope.deleteFiles('files/set1');
httpBackend.expectPOST('deleteFiles.php');
httpBackend.flush();
expect(scope.showFiles).toHaveBeenCalled();
});
});
Error message: Expected a spy, but got Function.
I do not how to use spy in here and I do not understand what should I have in httpBackend .respond

When you promise is resolved successfully then $scope.showFiles(source, 'source'); function is called. So create a spy on showFiles.
it('should call showFiles if sourcepath is source', function() {
spyOn(scope, 'showFiles');
scope.source = 'files/set1';
scope.deleteFiles('files/set1');
httpBackend.expectPOST('deleteFiles.php');
httpBackend.flush();
expect(scope.showFiles).toHaveBeenCalled();
});

it('', function() {
spyOn(scope, 'showFiles');
scope.source = 'files/set1';
scope.deleteImages('images/set1');
// rest of your test ...
});

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.

Unsure on how to successfully test this function using $httBackend

This is the function in the controller:
var vm = this;
vm.getData = getData;
function getData(val) {
return $http.get('/get-data', {
params: {
query: val
}
}).then(function(response) {
return response.data;
});
}
and this is my (stripped down) test file:
describe('Controller: MyCtrl', function() {
'use strict';
var MyCtrl;
var rootScope;
var scope;
var httpMock;
beforeEach(function() {
module('MyModule');
inject(function($controller, $rootScope, $httpBackend) {
rootScope = $rootScope;
scope = $rootScope.$new();
httpMock = $httpBackend;
MyCtrl = $controller('MyCtrl as vm', {
$rootScope: rootScope,
$scope: scope,
$http: httpMock,
});
});
});
describe('vm.getData()', function() {
it('returns the required data', function() {
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
httpMock.flush();
expect(scope.vm.getData('test-val')).toEqual('test-data');
});
});
});
I would like to test that the result if calling getData() return the correct data.
Currently I'm getting the error $http.get is not a function. Setting a breakpoint in my function shows that http is stubbed with $httpBackend though.
I think there is something fundamental I'm not grasping - any pointers would be greatly appreciated.
You shouldn't have to create the controller with:
$http: $httpBackend
to mock the backend. $httpBackend will mock the request itself already.
Further the test and assertion is done in the wrong order:
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
MyCtrl.getData('test-val').then(function(_result_){ //perform the request
result = _result_; //save the result of the promise
});
httpMock.flush(); //execute the request
expect(result).toBe('test-data'); //assert that the result is as expected

Method undefined in Angular JS service unit test

I am writing unit tests for my existing AngularJS app. There are just four methods in this service. I was able to getFollowUpList to work, but refresh() is not working and it is a very simple method.
The refresh method should simply set deferredGetFollowUpList = null and return true in my test.
There error I'm getting is: TypeError: Cannot read property 'then' of undefined, so my refresh method is undefined. Why is this the case? Thanks
Service
(function () {
"use strict";
angular
.module("all.patient.details")
.factory("followUpListService", ["$rootScope", "$http", "userContext", "$q", "$uibModal", "htmlBaseUrl", FollowUpListService]);
function FollowUpListService($rootScope, $http, userContext, $q, $uibModal, htmlBaseUrl) {
var deferredGetFollowUpList = null;
return {
getFollowUpList: getFollowUpList,
displayModal: displayModal,
refresh: refresh,
save: save
}
function refresh() {
deferredGetFollowUpList = null;
}
}
})();
Unit Test
describe("followUpListService", function () {
beforeEach(module("all.patient.details"));
var followUpListService = {};
var $httpBackend;
var htmlBaseUrlMock;
var returnedFollowUpListData;
var deferredGetFollowUpList;
var $rootScope;
var $q;
var $uibModal;
beforeEach(function () {
htmlBaseUrlMock = { format: function () { } };
module(function ($provide) {
$provide.value("htmlBaseUrl", htmlBaseUrlMock);
});
inject(function (_$rootScope_, _$httpBackend_, _$q_, _$uibModal_, _followUpListService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
$q = _$q_;
$uibModal = _$uibModal_;
followUpListService = _followUpListService_;
});
});
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it("calls refresh()", function () {
followUpListService.refresh()
.then(function (data) {
deferredGetFollowUpList = data;
});
expect(deferredGetFollowUpList).toBe(null);
});
As deferredGetFollowUpList is service level variable, Can you write your test as -
followUpListService.deferredGetFollowUpList = data; //Any Mock Data
followUpListService.refresh();
expect(followUpListService.deferredGetFollowUpList).toBe(null);

AngularJS Testing and $http

So Im trying to figure out how to write unit tests for my angular controller. I am using karma as my runner. I was able to write 1 successful test but every time I try to write another test it yells at me about unexpected calls and such.
Here is my controller im trying to test.
(function (angular) {
'use strict';
var ngModule = angular.module('myApp.dashboardCtrl', []);
ngModule.controller('dashboardCtrl', function ($scope, $http) {
//"Global Variables"
var vm = this;
vm.success = false;
vm.repos = [];
//"Global Functions"
vm.addRepository = addRepository;
vm.listRepos = listRepos;
//Anything that needs to be instantiated on page load goes in the init
function init() {
listRepos();
}
init();
// Add a repository
function addRepository(repoUrl) {
$http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
//Lists all repos
function listRepos() {
$http.get('/api/repo').then( function (response){
vm.repos = response.data;
});
}
});
}(window.angular));
So I have a test written for listRepos(). It goes as follows
describe('dashboardCtrl', function() {
var scope, httpBackend, createController;
// Set up the module
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $httpBackend, $controller) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
createController = function() {
return $controller('dashboardCtrl', {
'$scope': scope
});
};
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should call listRepos and return all repos from the database', function() {
var controller = createController();
var expectedResponse = [{id: 12345, url: "https://github.com/myuser/myrepo.git"}];
httpBackend.expect('GET', '/api/repo')
.respond(expectedResponse);
httpBackend.flush();
scope.$apply(function() {
scope.listRepos;
});
expect(controller.repos).toEqual(expectedResponse);
});
This works and the test passes. Now my problem is I want to write another test to test the other function that calls a new api endpoint.
This is the test im trying to write for addRepository.
it('should addRepository to the database', function() {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
httpBackend.flush();
scope.$apply(function() {
scope.addRepository(givenURL);
});
expect(controller.success).toBe(true);
expect(controller.listRepos).toHaveBeenCalled();
});
The error I get when I add this test to the spec is:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.8/$rootScope/inprog?p0=%24digest
The example I am working with is this one here
Any suggestions or tips is greatly appreciated!
UPDATE:
So changed my function to return the promise from the $http.post,
I rewrote my 2nd test and also wrapped my first test in a describe block describing the function its trying to test.
With the following:
describe('addRepository', function () {
it('should addRepository to the database', function () {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
scope.$apply(function () {
scope.addRepository(givenURL);
});
httpBackend.flush();
expect(controller.success).toBe(true);
});
it('should call listRepos', function() {
var controller = createController();
httpBackend.expect('GET', '/api/repo').respond('success');
controller.controller().then(function (result) {
expect(controller.listRepos).toHaveBeenCalled();
});
httpBackend.flush();
});
});
I still get the error:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
but also
TypeError: 'undefined' is not a function (evaluating 'controller.controller()')
Error: Unflushed requests: 1
which shows 2 tests failed.
The flush should come after the call to the function. I'd also change the function to return the promise from the $http.post:
// Add a repository
function addRepository(repoUrl) {
return $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
And then in the test you can call it and test the success part:
EDIT
I changed the controller.controller() to what you have.
it('should call listRepos', function() {
// Your setup
ctrl.addRepository().then(function(result) {
expect(ctrl.listRepos).toHaveBeenCalled();
});
});
EDIT 2
I emulated as best i could your code and the tests I write for the code:
(function () {
'use strict';
angular
.module('myApp')
.controller('DashboardController',DashboardController);
DashboardController.$inject = ['$http'];
function DashboardController($http) {
var vm = this;
vm.success = false;
vm.repos = [];
vm.addRepository = addRepository;
vm.listRepos = listRepos;
init();
// Anything that needs to be instantiated on page load goes in the init
function init() {
vm.listRepos();
}
// Add a repository
function addRepository(repoUrl) {
return $http.post('http://jsonplaceholder.typicode.com/posts/1.json').then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
vm.listRepos();
});
}
// Lists all repos
function listRepos() {
return $http.get('http://jsonplaceholder.typicode.com/posts/1').then( function (response){
vm.repos = response.data;
});
}
};
}());
Here I'm using an online JSONPlaceholder API to simulate HTTP calls as I, obviously, can't hit what you're pointing at. And for the test (which all pass):
(function() {
'use strict';
fdescribe('DashBoardController', function() {
var $rootScope, scope, ctrl, $httpBackend;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$httpBackend_,$controller) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$httpBackend =_$httpBackend_;
ctrl = $controller('DashBoardController',{
$scope: scope
});
}));
beforeEach(function() {
// Setup spies
spyOn(ctrl,'listRepos');
});
describe('controller', function() {
it('should be defined', function() {
expect(ctrl).toBeDefined();
});
it('should initialize variables', function() {
expect(ctrl.success).toBe(false);
expect(ctrl.repos.length).toBe(0);
});
});
describe('init', function() {
it('should call listRepos', function() {
$httpBackend.expectGET('http://jsonplaceholder.typicode.com/posts/1')
.respond({success: '202'});
$httpBackend.expectPOST('http://jsonplaceholder.typicode.com/posts/1.json')
.respond({success: '202'});
ctrl.addRepository().then(function(result) {
expect(ctrl.success).toBe(true);
expect(ctrl.repoUrl).toBe('');
expect(ctrl.listRepos).toHaveBeenCalled();
});
$httpBackend.flush();
});
});
});
}());

How to call a basic javascript function from jasmine test spec?

I'm trying to write test cases for a javascript method but my jasmine is not able to call the js method. It is giving me error that the function is not defined.
Below is Controller Code
(function () {
"use strict";
angular.module('rbApp').controller('PQRCtrl', PQRCtrl);
PQRCtrl.$inject = ['$scope', '$state', 'baseURL', 'PQRCtrlFactory', '$cookieStore'];
function PQRCtrl($scope, $state, baseURL, PQRCtrlFactory, $cookieStore) {
var funcB= function(){
console.log("This line should called from jasmine ")
}
}
}());
My jasmine spec
describe('PQRCtrl Controller Test Suite', function() {
var mockPQRCtrl,mockrootScope, mockscope, mockstate,mockcookies, mockstateparams, mockgetSourcePostsService, mockPQRCtrlFactory;
var baseURL = "****";
beforeEach(module('rbApp'));
beforeEach(module("ui.router"));
beforeEach(module("ngRoute"));
beforeEach(module("ngCookies"));
beforeEach(module("ui.bootstrap"));
beforeEach(module("toggle-switch"));
beforeEach(module("ngFileUpload"));
beforeEach(module("ngDraggable"));
beforeEach(module("angular-loading-bar"));
beforeEach(module("ngAnimate"));
beforeEach(module("ngGrid"));
beforeEach(module("kendo.directives"));
beforeEach(module('ui.bootstrap'));
beforeEach(inject(function($injector, _$rootScope_, _$controller_, _$httpBackend_, _$stateParams_, _$state_,_$cookieStore_,_PQRCtrlFactory_){
var userData = {'token' : 'abc'};
mockrootScope = _$rootScope_;
mockstateparams = _$stateParams_;
mockscope = mockrootScope.$new();
mockstate = _$state_;
mockcookies = _$cookieStore_;
mockPQRCtrlFactory = _PQRCtrlFactory_;
mockcookies.put('userInfo', userData);
var userInfo = mockcookies.get('userInfo');
var token = userInfo.token;
mockPQRCtrlFactory = _$controller_('PQRCtrl',
{
'$scope': mockscope,
'$state': mockstate,
'baseURL' : baseURL,
'PQRCtrlFactory': mockPQRCtrlFactory ,
'$cookieStore' : mockcookies
});
}));
it("Test: PQRCtrl Controller exists or not", function() {
expect(mockPQRCtrl).toBeDefined();
});
it("Test: funcB() Success", function() {
funcB(); // its not calling my actual function in the controller
// it is saying that its not defined
})
});
Your funcB is defined inside the controller PQRCtrl. Put it outside:
(function () {
"use strict";
var funcB;
angular.module('rbApp').controller('PQRCtrl', PQRCtrl);
PQRCtrl.$inject = ['$scope', '$state', 'baseURL', 'PQRCtrlFactory', '$cookieStore'];
function PQRCtrl($scope, $state, baseURL, PQRCtrlFactory, $cookieStore) {
funcB = function(){
console.log("This line should called from jasmine ")
}
}

Resources