I am trying to write a unit test for service that performs a http.post to an api that passes credentials in the header.
Controller:
app.controller('LoginController', function($scope, $http, signInService) {
$scope.LogIn = function(usrnm, pwd) {
signInService.authUser(usrnm, pwd)
.success(function (data, status, headers, config) {
// Display success message
$scope.gotToAddress = data.successUrl;
})
.error(function (data, status, headers, config) {
// Display error message
}
}
});
signInService:
app.service('signInService', function($http) {
this.authUser = function (usrnm, pwd) {
return $http({
url: '/api/json/authenticate',
method: "POST",
data: '{}',
headers: {
'Content-Type': 'application/json',
'X-app-Username': usrnm,
'X-app-Password': pwd
}
});
};
});
Unit test:
describe('mocking service http call', function() {
beforeEach(module('myApp'));
var LoginController, $scope;
describe('with httpBackend', function() {
beforeEach(inject(function($controller, $rootScope, $httpBackend) {
$scope = $rootScope.$new();
$httpBackend.when('POST', '/api/json/authenticate', {}, function(headers) {
return {
'Content-Type': 'application/json',
'X-app-Username': 'admin',
'X-app-Password': 'admin'
};
}).respond(200)
LoginController = $controller('LoginController', { $scope: $scope });
$httpBackend.flush();
}));
it('should set data to "things and stuff"', function() {
expect($scope.data).toEqual({things: 'and stuff'});
});
});
});
When running the test i am seeing the following error: mocking service http call ยป with httpBackend
Error: No pending request to flush !
Controller with service on .succeed:
app.controller('LoginController', function($scope, $http, signInService, cookieSrv) {
$scope.LogIn = function(usrnm, pwd) {
signInService.authUser(usrnm, pwd)
.success(function (data, status, headers, config) {
// Display success message
var cookieID = 'myCookie';
cookieSrv.createCookie(cookieID, data.token, 3, data.redirectUrl);
})
.error(function (data, status, headers, config) {
// Display error message
}
}
});
cookieSrv.js
app.service('cookieSrv', function() {
return {
createCookie : function(cookieID, token, days, redirectUrl) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = cookieID+"="+token+expires+"; path=/";
window.location.assign(redirectUrl)
}
}
});
Your controller defines a method logIn on the $scope but you do not call this function in the test, and hence actual http request is not made.
Modify the test by calling $scope.logIn before you call flush
LoginController = $controller('LoginController', { $scope: $scope });
$scope.logIn("Test","test"); // Add this
$httpBackend.flush();
Related
Although there are many questions regarding the subject , yet I am unable to figure it out , how to proceed further.
I am new in AngularJS. I want to pass data coming from API in Controller and pass it to another function. For this I know I have to create a Service. But after coming to this extend of code I am unable to figure it, how to store it in Service and pass it on other Controller or of function within same Controller. I am new in making Service.
Controller:
$scope.GetR = function (){
$scope.X = null;
$scope.Y = null;
$http({method: 'POST', url: 'http://44.43.3.3/api/infotwo',
headers: {"Content-Type": "application/json"},
data: $scope.ResponseJson
})
.success(function(data, status, headers, config) {
$scope.X = data.X;
$scope.Y = data.Y;
//console.log($scope.X+"and"+$scope.Y);
//Seding RS to API to get AAs
$scope.RJson = {
"ICl": $scope.ICl,
"RS": $scope.X
};
$http({method: 'POST', url: 'http://44.128.44.5/api/apithree',
headers: {"Content-Type": "application/json"},
data: $scope.RJson
})
.success(function(data, status, headers, config) {
$scope.At = data;
$scope.Eq = data.AA.Eq;
$scope.FIn = data.AA.FIn;
$scope.MM = data.AA.MM;
console.log("Eq:"+$scope.Eq+" FIn:"+$scope.FIn+" MM:"+$scope.MM);
}).error(function(data, status, headers, config) {
console.log("API failed...");
});
}).error(function(data, status, headers, config) {
console.log("Something went wrong...");
});
};
Now I want to pass this data to Service so that I can call this output on other API input
.success(function(data, status, headers, config) {
$scope.At = data;
$scope.Eq = data.AA.Eq;
$scope.FIn = data.AA.FIn;
$scope.MM = data.AA.MM;
console.log("Eq:"+$scope.Eq+" FIn:"+$scope.FIn+" MM:"+$scope.MM);
This shows how to create a service and share data between two controllers.
The service:
(function() {
'use strict';
angular
.module('myAppName') // Replace this to your module name
.service('MyService', MyService);
MyService.$inject = [];
function MyService() {
this.data = null;
}
})();
First controller:
(function() {
'use strict';
angular
.module('myAppName') // Replace this to your module name
.controller('MyFirstController', MyFirstController);
MyFirstController.$inject = ['MyService', '$http'];
function MyFirstController(MyService, $http) {
var vm = this;
vm.data = MyService.data;
$http.post('/someUrl', whatEverData).then(resp=> {
MyService.data = resp.data;
})
}
})();
Second controller:
(function() {
'use strict';
angular
.module('myAppName') // Replace this to your module name
.controller('MySecondController', MySecondController);
MySecondController.$inject = ['MyService', '$http'];
function MySecondController(MyService, $http) {
var vm = this;
vm.data = MyService.data; // Here you can use the same data
}
})();
Not sure if this is what you are looking for. Below code is not tested (May have syntax errors)
Service:
function() {
'use strict';
angular
.module('myAppName')
.factory('MyService', MyService);
MyService.$inject = [];
function MyService() {
var data = null;
return {
getData: function() {
return data;
},
setData: function(d) {
data = d;
}
}
}
})();
Controller:
(function() {
'use strict';
angular
.module('myAppName')
.factory('controller', controller);
controller.$inject = ['$scope', '$http', 'MyService'];
function controller($scope, $http, MyService) {
$scope.GetR = function() {
$scope.X = null;
$scope.Y = null;
var promise = $http({
method: 'POST',
url: 'http://44.43.3.3/api/infotwo',
headers: {
"Content-Type": "application/json"
},
data: $scope.ResponseJson
});
promise.success(function(data, status, headers, config) {
$scope.X = data.X;
$scope.Y = data.Y;
//console.log($scope.X+"and"+$scope.Y);
//Seding RS to API to get AAs
$scope.RJson = {
"ICl": $scope.ICl,
"RS": $scope.X
};
}).error(function(data, status, headers, config) {
console.log("Something went wrong...");
});
return promise;
};
$scope.sendRS = function() {
var promise = $http({
method: 'POST',
url: 'http://44.128.44.5/api/apithree',
headers: {
"Content-Type": "application/json"
},
data: $scope.RJson
});
promise.success(function(data, status, headers, config) {
$scope.At = data;
$scope.Eq = data.AA.Eq;
$scope.FIn = data.AA.FIn;
$scope.MM = data.AA.MM;
console.log("Eq:" + $scope.Eq + " FIn:" + $scope.FIn + " MM:" + $scope.MM);
}).error(function(data, status, headers, config) {
console.log("API failed...");
});
return promise;
}
var init = function() {
$scope.GetR().then(function() {
$scope.sendRS().then(function(data) {
MyService.setData({
At: data,
Eq: data.AA.Eq,
FIn: data.AA.FIn,
MM: data.AA.MM
});
})
})
}
init();
}
})();
Other controller
(function() {
'use strict';
angular
.module('myAppName')
.controller('controller1', controller1);
controller1.$inject = ['$scope', 'MyService'];
function controller1($scope, MyService) {
$scope.data = MyService.getData();
}
})();
As soon as Html page gets loaded, it calls SuperCategoryController, where i am assigning supercategories to $scope variable.
$scope.SuperCategories = SuperCategoryService.GetSuperCategories();
But as this controller is depends on service, which in turn calls the http request. so at the time pf assignment http request is not completed. so $scope.SuperCategories is getting assiged to undefined.
sampleApp.service('SuperCategoryService', ['$http', function ($http){
var URL = 'http://localhost/cgi-bin/superCategory.pl';
var SuperCategories;
$http({
method: 'POST',
url: URL,
data: "action=GET",
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).
success(function (data) {
alert (data);
if (data != null || data != 'undefined') {
SuperCategories = data;
}
})
.error(function (error) {
alert (error.message);
//$scope.status = 'Unable to retrieve super categories' + error.message;
});
//simply returns the SuperCategories list
this.GetSuperCategories = function () {
//alert (SuperCategories);
return SuperCategories;
}
}]);
sampleApp.controller('SuperCategoryController', ['$scope', 'SuperCategoryService', function ($scope, SuperCategoryService){
$scope.SuperCategories = SuperCategoryService.GetSuperCategories();
$scope.LoadSuperCategoryMapping = function()
{
alert ($scope.SelectedSuperCategory.id);
}
}]);
How to solve this problem in proper way.
I haven't tried this code myself but I would approach a solution using factory and a promise to make sure the data has been loaded. Something along these lines:
sampleApp.factory('SuperCategoryService', ['$http', function ($http){
return {
GetSuperCategories: function () {
var URL = 'http://localhost/cgi-bin/superCategory.pl';
return $http.get(URL);
}
}
}]);
sampleApp.controller('SuperCategoryController', ['$scope', 'SuperCategoryService', function ($scope, SuperCategoryService){
$scope.SuperCategories = function() {
SuperCategoryService.GetSuperCategories()
.then(function(d) {
if (d.data != undefined) {
// Your data should be loaded here
console.log(d.data);
$scope.SuperCategories = d.data;
}
})
.error(function(data, status) {
// Errors here
});
}
}]);
I'm trying to secure the partial views so user cannot't switch views without a login but having trouble. The code is below:
var loginApp = angular.module('loginApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ngRoute'
])
//
//
//
// -------------- Cannot get this to work ------------
//
//
//
// loginApp.factory('authInterceptor', function ($q, $location) {
// debugger;
// return {
// request: function (config) {
// config.headers = config.headers | {};
// if (localStorage.auth_token) {
// config.headers.token = localStorage.auth_token;
// }
// return config;
// },
// responseError: function (response) {
//
// if (response.status === 401 || response.status === 500) {
// $location.path('/');
// }
// return $q.reject(response);
// }
// }
// })
//
// loginApp.config(function ($httpProvider) {
// $httpProvider.interceptors.push('authInterceptor');
// })
loginApp.config(function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/login.html',
controller: 'loginController'
})
.when('/expertView', {
templateUrl: 'views/b.html',
controller: 'bViewController'
})
});
loginApp.controller('bViewController', function ($scope) {
//$scope.message = 'Everyone come and see how good I look!';
});
var loginController = function ($scope, $http, $location) {
$scope.user = {};
$scope.user.username = "name";
$scope.user.userID = "123456";
$scope.user.password = "444444444";
$scope.user.ui = "true";
$scope.user.submitForm = function (item, event) {
var data = {
userID: $scope.user.userID,
password: $scope.user.password,
ui: $scope.user.ui
};
$http({
url: '/api/v1/auth/login',
method: "POST",
dataType: "json",
data: data,
headers: {
'Accept': 'application/json, text/javascript',
'Content-Type': 'application/json; charset=utf-8'
}
}).success(function (data, status, headers, config) {
console.log("Success!");
}).error(function (data, status, headers, config) {
console.log("Submitting to Server failed!");
});
}
}
I just need to secure the views and make sure user cannot (switch) access views without login.
First create a constant that can determine the access level for each route for example
angular.module("App")
.constant('USER_ROLES', {
logedIn : 'true'
});
then add them to the definition of the route as
.when('/write',{
templateUrl:'templates/write.html',
access_level:USER_ROLES.logedIn
})
After that in the run function check $rootScope.$on('$locationChangeStart' event and inside it you can access the route by var location = $location.path(); var route = $route.routes[location];and then access the user role by route.access_level;
I have:
this.isAuthenticated = function() {
var deferred;
deferred = $q.defer();
$http({
url: "" + endpoints.authService + "api/v1/session/check",
method: 'GET',
withCredentials: true
}).success(function(response) {
var user;
if (response.authenticated === true) {
user = response;
}
return deferred.resolve(response);
}).error(function(data, status, headers, config) {
deferred.reject(data);
return $rootScope.$broadcast('api-error', 'Cannot access authentication service');
});
return deferred.promise;
};
Assuming that the endpoint is down, apparently it tries to do the call infinitely. Is this some known Angular behavior? And can I disable it?
No factory making http call when your controller invoking that call please see here: http://plnkr.co/edit/u7YSD8gkbOSKN32SU62P?p=preview. It's rather you controller keeping call factory function.
var app = angular.module('plunker', []);
app.factory('dataService', function($http, $q, $rootScope) {
var endpoints = {
authService: "ttest"
};
this.isAuthenticated = function() {
var deferred;
deferred = $q.defer();
$http({
url: "" + endpoints.authService + "api/v1/session/check",
method: 'GET',
withCredentials: true
}).success(function(response) {
var user;
if (response.authenticated === true) {
user = response;
}
return deferred.resolve(response);
}).error(function(data, status, headers, config) {
deferred.reject(data);
return $rootScope.$broadcast('api-error', 'Cannot access authentication service');
});
return deferred.promise;
};
return this;
});
app.controller('MainCtrl', function($scope, dataService) {
$scope.$on('api-error', function(a, b){
alert(b);
});
dataService.isAuthenticated();
});
just put this deferred.resolve(response); instead of return deferred.resolve(response);
and also remove return from :
return $rootScope.$broadcast('api-error', 'Cannot access authentication service');
I am writing QUnit tests for an AngularJS factory. Here's the code for the factory:
var app = angular.module('App', []);
app.factory('$groupFactory', function($rootScope, $http) {
return {
'getAll': function(_callback) {
$http.get("get/values/from/server", {
headers: {
'Content-type': 'application/json'
}
}).success(function(data, status, headers, config) {
_callback(data);
}).
error(function(data, status, headers, config) {
_callback(data);
});
},
}
});
Also see the below Qunit test cases. The test-1 gets the http response from the $httpBackend works, but in test-2, it doesn't.
var $scope,
$rootScope,
$http,
$httpBackend,
$groupFactory,
injector = angular.injector(['ng', 'App', 'ngMockE2E']),
init;
init = {
setup: function() {
$rootScope = injector.get('$rootScope').$new();
$groupFactory = injector.get('$groupFactory');
$httpBackend = injector.get('$httpBackend');
$httpBackend
.when('GET', "get/values/from/server")
.respond({'response': 'success'});
}
};
module('$groupFactory', init);
// test-1
test("getAll", function() {
expect(1);
$groupFactory.getAll(function(data) {
equal(data.response, 'success', "success casse");
start();
});
stop();
});
// test-2
test("getAll", function() {
expect(1);
$httpBackend.expectGET("get/values/from/server").respond(404, {
response: 'failure'
});
$groupFactory.getAll(function(data) {
equal(data.response, 'failure', "failure casse");
start();
});
stop();
});
Any idea why is it not working?
Here is a demo on jsFiddle.
Calling $httpBackend.flush() after stop() will work:
stop();
$httpBackend.flush();
Here's the updated demo.