Unable to access scope variable in jasmine test case - javascript

I have a controller defined in my module as follows,
myTestApp.controller("LoginController", function($scope,$http,$location) {
$scope.submitForm = function(){
var userEmail = $scope.user.email;
var userPassword = $scope.user.password;
$http({
url:'api/login/',
headers: {'Content-Type': 'application/json'},
method:'POST',
data:{
username:userEmail,
password:userPassword
}})
.success(function(data){
$scope.loginResult = data;
});
};
});
I am trying to write a test case for mocking the HTTP call using httpBackend.
Here is my jasmine test code:
describe('make HTTP mock call', function() {
var scope, httpBackend, http, controller;
beforeEach(module('myTestApp'));
describe('with httpBackend', function() {
beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
http = $http;
controller = $controller('LoginController', {$scope : scope} );
httpBackend.when("POST", "api/login/",{'username':'test#gmail.com', 'password':'test'}, function(headers) {
return {
'Content-Type': 'application/json',
};
}).respond(200);
}));
afterEach(inject(function ($httpBackend){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
}));
it('to check if user is valid', inject(function ($http) {
var expectedResponse = { success: true };
httpBackend.expectPOST('api/login/', {
"username": "test#gmail.com",
"password": "test"
}, function(headers){
return headers['Content-Type'] === 'application/json';
}).respond(function(method, url, data, headers, params){
return [200, {}, {}];
});
//actual HTTP call
$http({
url:'api/login/',
headers: {'Content-Type': 'application/json'},
method:'POST',
data:{
username:"test#gmail.com",
password:"test"
}});
//flush response
httpBackend.flush();
expect(scope.loginResult).toEqual(expectedResponse);
}));
});
});
However, when I run the test case, I get the below error
Expected undefined to equal Object({ success: true }).
I want to compare the response data from my HTTP call to the expectedResponse and if both are equal the test should pass.
However, I am not able to access the $scope variable loginResult from within my test case. can anyone tell me what I am doing wrong over here?
can someone describe the best way to mock the HTTP calls using Jasmine?

Your login data will actually be populated only when function submitForm will be called. So in the test, call the function before doing the flush() .
Update:
Make sure you have defined the
$scope.user={email :'test#gmail.com,password:'test'}
at the first line of your test (just after it('to check if user is valid', inject(function ($http) {)

Related

How to call php file via factory/service method using Angular.js

I need to call php file using service/Factory method using Angular.js. Here instead of calling $http repeatedly in each file to call diferent php file for different purpose, I need to make it common. I am explaining one example below.
logincontroller.js:
var loginAdmin=angular.module('Takeme');
loginAdmin.controller('loginController',function($scope,$http,$location,$window,inputField){
$http({
method: 'POST',
url: "php/Login/verify.php",
data: userData,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(function successCallback(response){
},function errorCallback(response) {
});
}
I have one common route.js file which is common for all controller and given below.
route.js:
var Admin=angular.module('Takeme',['ui.router', '720kb.datepicker','ngMessages','ngCapsLock','ui.bootstrap','ngFileUpload','angularUtils.directives.dirPagination']);
Admin.run(function($rootScope, $state) {
$rootScope.$state = $state;
});
Admin.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('/',{
url: '/',
templateUrl: 'view/login.html',
controller: 'loginController'
})
})
Admin.factory('inputField',function($timeout,$window){
return{
borderColor:function(id){
$timeout(function() {
var element = $window.document.getElementById(id);
if(element){
element.focus();
element.style.borderColor = "red";
}
});
},
clearBorderColor:function(id){
$timeout(function() {
var element = $window.document.getElementById(id);
if(element){
element.style.borderColor = "#cccccc";
}
});
}
};
});
Here I need to that $http service to call the php file common for which in every controller I will call that $http repeatedly. I need to pass only the parameters for $http service and return the response.
create a factory/service
angular.module('myApp').factory('DataService', DataService);
DataService.$inject = ['$http', '$q'];
function DataService($http, $q) {
return {
getData: getData,
}
function getData(userData) {
var deferred = $q.defer();
$http({
method: 'POST',
url: "php/Login/verify.php",
data: userData,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(function(response) {
deferred.resolve(response.data);
},
function(error) {
deferred.reject(error.data);
});
return deferred.promise;
};
}
then use this factory whenever you need in a controller
angular.module('myApp')
.controller('MyController', ['$scope', 'DataService',
function($scope, DataService ) {
$scope.getMyData = function() {
var data = {};
DataService.getData(data)
.then(function(response) {
}, function(error) {
});
};
}
]);

AngularJS: console.log does not display anything

I wrote a controller for login page. Here is my controller:
var authApp = angular.module('loginApp', [])
authApp.controller('LoginCtrl', ['$scope', '$location', 'loginFactory', function($scope, $location, loginFactory){
$scope.authenticate = function() {
loginFactory.login($scope.username, $scope.password)
.then(function(response) {
console.log(response.$statusText);
}, function errorCallBack(response) {
console.log(response.$statusText);
});
}
}]);
My service:
authApp.factory("loginFactory", function ($http) {
return{
login: function(username, password) {
var data = "username="+username+"&password="+password+"&submit=Login";
return $http({
method: 'POST',
url: 'http://localhost:8080/login',
data: data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
});
}
When I debug the code, authentication seems successful and it did get into then function. However nothing displays in console. And I got a warning(?) saying undefined for the line console.log(response.$statusText);. It is not an error since it is not red. Why doesn't it print out anything?
Use response.statusText not response.$statusText. The documentation for AngularJS $http requests lists statusText as one of the properties of the response object - https://docs.angularjs.org/api/ng/service/$http

Use Global variable in angularjs service

I have to use global variable in angular service.
I have function to get session id from parent application. like this
var sessionID;
function AddSessionID(e) {
sessionId = e;
}
This is the function I have used for get sessionid and this function called in parent app.
I need to use this session id(sessionID) inside angular service as a parameter.
this is my service call
(function () {
"use strict";
mApp.service('compasService', ['$http', '$q', function ($http, $q) {
//Public API
return {
initialLayout: initialLayout
};
function initialLayout() {
var dataObj = {};
dataObj.command = 'getLayoutCstics';
dataObj.sessionID = sessionId;
dataObj.userid = userID;
var transform = function (data) {
return $.param(dataObj);
},
request = $http({
method: "POST",
url: myURL,
//params:For passing via query string
transformRequest: transform,
dataType: "text",
//To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
crossDomain: true
});
return (request.then(successHandler, erroHandler));
}
})();
Please get me a proper way to get it.
Architectually speaking, if you must use global variables, you want to limit the places that must use the global context. In this case, you might benefit from a sessionService in Angular that encapsulates access to the session Id.
(function(global) {
'use strict';
mApp.service("sessionService", function() {
return {
getSessionID: function() {
return global.sessionId;
},
getUserID: function() {
return global.userID;
}
};
});
}(this));
Then you can specify that as a dependency in your other Angular services:
(function () {
"use strict";
mApp.service('compasService', ['$http', '$q', 'sessionService', function ($http, $q, sessionService) {
//Public API
return {
initialLayout: initialLayout
};
function initialLayout() {
var dataObj = {
command: 'getLayoutCstics',
sessionID: sessionService.getSessionID(),
userId: sessionService.getUserID()
},
transform = function (data) {
return $.param(dataObj);
},
request = $http({
method: "POST",
url: myURL,
//params:For passing via query string
transformRequest: transform,
dataType: "text",
//To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
crossDomain: true
});
return (request.then(successHandler, erroHandler));
}
})();
The successHandler and erroHandler appear to be global functions as well. The second function, erroHandler appears to be misspelled and should be errorHandler (notice the "r" before the "H"), though I don't know if the spelling is an actual problem.
The whole point is to encapsulate access to global variables in one or more services so you limit your use of globals in your other services, modules and controllers.

Async nature of AngularJS with digest/apply

I've often had a problem where I had a scope variable set up in a parent controller, and the child controller calls this scope variable. However, it calls it before the function has been able to set the scope element, causing it to return undefined. Example:
Parent Controller:
module.controller('parent', '$scope', '$http', function ($scope, $http) {
$scope.init = function(profileID, profileViewStatus) {
//Initiiaze user properities
$http.get(requestUserInformationGetURL + profileID)
.success(function(profile) {
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
console.log($scope.userID);
})
.error(function() {
exit();
});
}
Child Controller:
module.controller('child', function($scope, $http, fetchInfo) {
console.log($scope.userID);
//Fetch the HTTP POST data for the user profile
var promise = $http({
method: "post",
url: fetchInfo,
data: {
user_id: $scope.userID //From the parent controller
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
//Populate the scope, log the data
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});
HTML:
<div ng-controller="parent" init="init('<?php $user_id;?>')">
<div ng-controller="child">
</div>
</div>
What often happens is that the userID will be reported back as undefined in the child controller, but then right after, it will be reported back as defined in the parent controller. Obviously, the child controller using the $scope.userID is being called before the init function in the parent controller is complete. How do I force AngularJS to wait in the child controller until the init function is complete? I've tried something like:
if (!$scope.userID) {
$scope.$digest();
}
But it didn't work and I don't think it's the correct syntax. I guess, I don't fully understand the Asycn nature of AngularJS and this occurs multiple times. How do you control the DOM loading elements to solve something like this problem?
Proper way in this case would be to use dedicated service to handle async operations, requests, data caching, etc. But since you don't have service layer yet, I will propose simple Promise-based solution using controller scope promise object.
Check you modified code:
module.controller('parent', ['$scope', '$http', function ($scope, $http) {
$scope.init = function (profileID, profileViewStatus) {
$scope.profilePromise = $http.get(requestUserInformationGetURL + profileID).success(function (profile) {
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
})
.error(exit);
}
}]);
module.controller('child', function($scope, $http, fetchInfo) {
// Fetch the HTTP POST data for the user profile
$scope.profilePromise.then(function() {
return $http({
method: "post",
url: fetchInfo,
data: { user_id: $scope.userID },
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
})
.then(function(successResponse) {
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});
});
As you can see, parent controller init method is still called, but now it immediately sets scope property profilePromise, which is accessible in child controller.
Child controller uses then method of the parent controller profilePromise object, which guaranties that $http request using $scope.userID will fire after profile is already available.
Generally you would use a route resolve with the UI Router to ensure the work is done before either controller is constructed. Child states automatically have access to the resolves of their parent.
//Router configuration
.state('app.inspections.list', {
url: '',
templateUrl: 'Template/parent',
controller: "Parent as vm",
resolve: {
profile: ['$http', function ($http) {
return $http.get(requestUserInformationGetURL + profileID)
.success(function(profile) {
console.log(profile.userID);
return profile;
})
.error(function() {
exit();
});
}]
}
}).state('parent.child', {
url: 'child',
templateUrl: 'Template/child',
controller: "Child as vm"
})
//parent controller
module.controller('parent', '$scope', 'profile', function ($scope, profile){
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
}
//child controller
module.controller('child', 'profile', function($scope, $http, fetchInfo, profile){
console.log(profile.userID);
//Fetch the HTTP POST data for the user profile
var promise = $http({
method: "post",
url: fetchInfo,
data: {
user_id: profile.userID //From the parent controller
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
//Populate the scope, log the data
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});
you can use promise ($q service) :try using this code:
parent controller :
$scope.init = function(profileID, profileViewStatus) {
var deferred = $q.defer();
$http.get(requestUserInformationGetURL + profileID)
.success(function(profile) {
$scope.profile = profile;
$scope.userID = profile.user_id;
$scope.username = profile.username;
deferred.resolve($scope.userID);
console.log($scope.userID);
})
.error(function() {
deferred.reject('error');
exit();
});
return deferred.promise;
}
Don't call init method in parent contrller.
in child controller:
$scope.init().then(function(userID){
var promise = $http({
method: "post",
url: fetchInfo,
data: {
user_id: userID //From the parent controller
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
promise.then(function(successResponse) {
//Populate the scope, log the data
console.log(successResponse);
$scope.data = successResponse.data;
}, function(error) {
alert(error);
});
})
.catch(function(error){
console.log('error');
})
Your problem might be that $.get is being called asynchronously, which is the default behavior. Your init method might actually be called in the order you're expecting but what's happening is:
Parent init is called
$.get is called, but the server's response is non-instantaneous
Child init is called
GET data bounces back from the server
$.get(..).success(function(data){...}); is called to deal with the data
I'd suggest what other people are, using promises to defer execution.

How to test an http request in my case?

I have a service like this.
It is simply just make a http get request.
angular.module('myApp').service('TESTService', ['$http',
function($http) {
var request = function(url) {
return $http({
method: 'GET',
url: url
});
};
return {
get: function(url) {
return request(url);
}
};
}
]);
Within my controller, I have called the service
TESTService.get('/api/product' + id).success(
function(result) {
console.log(result)
}
);
I need to write the unit test for it
describe('test here', function () {
var testCtrl, scope, httpBackend, testService;
// Initialize the controller and a mock scope
beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_, _TESTService_) {
scope = _$rootScope_.$new();
httpBackend = _$httpBackend_;
testService = _TESTService_;
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
it('should return http data', function() {
var productData = {
data: [
{
obj: {
id:'123'
}
}
]
}
httpBackend.expectGET('/api/product/' + id).respond(productData);
TESTService.get('/api/product/' + id).
then(function(data) {
var result = data;
})
httpBackend.flush();
expect(result).toEqual(productData)
});
}));
After running the test, I got
Error: Unexpected request: GET /api/product/undefined
How do I write the test to make sure it passes? Any ideas? Thanks a lot!
Your variable "id" seems to be undefined. If you throw in
var id = 123;
before this line:
httpBackend.expectGET('/api/product/' + id).respond(productData);
It would call /api/product/123 instead.
So maybe you were looking for this in the first place:
httpBackend.expectGET('/api/product/' + productData.data[0].obj.id).respond(productData);
TESTService.get('/api/product/' + productData.data[0].obj.id).
And so on... Hope it helps!
Try putting single quotes around the object that's passed into $http, i.e. $http({method: 'GET', 'url', url});
angular.module('myApp').service('TESTService', ['$http',
function($http) {
var request = function(url) {
return $http({
method: 'GET',
'url': url
});
};
return {
get: function(url) {
return request(url);
}
};
}
]);

Categories