How to fix the unit test issue in my case? - javascript

I am trying to unit test two functions codes and keep getting error of undefined object.
my controller
vm = this;
//always fire first in the app
vm.getCompany = function() {
api.getCompany(function(res){
//do stuff
})
}
//always fire second in the app
vm.getEmployee = function() {
api.getEmployee(function(res){
//do stuff
})
}
api service
var company;
function getCompany() {
var company;
var q = $q.defer();
var url = ‘something.com’;
anotherApi.getCompany(url).then(function(comp){
company = comp;
q.resolve(company)
})
}
function getEmployee = function() {
var name = company.name
var url = ‘something.com/’ + name;
var q = $q.defer();
anotherApi.getEmployee(url).then(function(employee){
q.resolve(employee)
})
}
unit test.
beforeEach(function(){
module(‘myApp);
inject(function ($injector) {
$controller = $injector.get('$controller');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
$httpBackend = $injector.get('$httpBackend');
api = $injector.get('api');
});
vm = $controller'myCtrl', {
$scope : $scope
});
})
describe (‘test’, function(){
it(‘should get company’, function(){
vm.getCompany();
$httpBackend.flush();
// stuff and works
})
it(‘should get Employee’, function(){
vm.getEmployee()
$httpBackend.flush();
//getting error says
//'undefined' is not an object (evaluating 'company.name’)
})
})
I am getting 'undefined' is not an object (evaluating 'company.name’)
under getEmployee function in service.
I have tried many different ways but still not sure how to fix it, can someone help me about it? Thanks!

What is the expected behavior of the service if getEmployee is called before getCompany is called? You should at least check for company being null before attempting to use it. Also, you may want to consider storing the company in a property that you can access in your service. NOTE: I'm prefixing the property name with an underscore just to make a distinction between the public api and this pseudo-private property:
{
_company: null,
getCompany: function() {
var self = this;
var url = '...';
return $http.get(url).then(function(comp){
self._company = comp;
return self._company;
});
},
getEmployee: function() {
var self = this;
if (!self._company) {
return null; //or throw error or return rejected promise: $q.reject('company is null')
} else {
var url = '...';
var name = self._company.name;
return http.get(url);
}
}
}
Lastly, you can (and should) test your service separately from your controller now. In your controller test, you can just spyOn your service methods without it calling through to the server. And when you test your service, you can just set the service._company to a mock value when testing the getEmployee method.

Issue is in your Service. "company" should be the object literal since you access .name over it else it will through an error which you have specified.
Try below code:
Service
var company = {};
function getCompany() {
$http.get(url).then(function(comp){
company = comp;
return company;
})
}
function getEmployee = function() {
var name = company.name
$http.get(url).then(function(employee){
// do stuff
}
}
It should work.

Related

How to pass a callback function to once().then() in angularjs?

I have a separate model and a controller for a teachers list.
My teacherModel.js is:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// I'm trying to pass this callback from the controller:
callback;
});
}
};
return Teacher;
}]);
Now from my controller I call the getAllTeachers() method with a callback function:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers(function() {
$scope.teachers = teacher;
console.log($scope.teachers);
});
}]);
Problem is console.log($scope.teachers); is not logging anything to the console. I don't think the callback is being executed at all.
Can someone help me to figure out what I'm doing wrong or suggest a better way to add functionality to the model data from controller after the data is asynchronously retrieved from firebase? Thanks.
You can leverage the fact that once returns a firebase promise so you can alter your code to the following:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
return ref.once('value').then(function(snapshot) {
return snapshot.val();
});
}
};
return Teacher;
}]);
This would behave similarly to any $http request where it returns a promise. Now, in your controller, you can then call your getAllTeachers() like so:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers().then(function (snapshotValues) {
// What you returned in the promise above is populated in snapshotValues here
$scope.teachers = snapshotValues;
});
}]);
Update
If you want to use the $q service for your particular scenario, you can do the following:
app.factory('Teacher', ['$q', function($q) {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var defer = $q.defer();
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
var val = snapshot.val();
// Transform your data any way you want.
// Whatever you pass into resolve() will be available as a parameter in the subsequent then()
defer.resolve(val);
});
return defer.promise;
}
};
return Teacher;
}]);
Using the method would still be the same. You simply just call then()
teacher.getAllTeachers()
.then(function (whatYouPassedInResolve) {
});
Another thing to note is that in the getAllTeachers method inside of your factory, I did not handle any error cases. That would be achieved by rejecting the promise with defer.reject(objectToSendBack). You pass in any data you want accessible when you deem that call a failure.
Just pass in a function for the second parameter to the `then(successCallback, errorCallback) to handle any rejected promises.
I think you are not calling the callback actually, use callback()
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// Try this
callback();
});
}
};
return Teacher;
}]);

Angularjs 1.5 - Component Communication using service - Async variable manipulation?

I am very new to this Angular component communication. I am using Angular 1.5.X version and I am using factory to share data between components. I am facing one issue where Async value of Service Variable refreshes after certain time.
I understand one solution is to set watcher on non scope variable but I think I am missing something important here. Can you guys please share views?
This is Service.js code
Service.$inject = ['$http','$q'];
function Service($http,$q) {
this.$http = $http;
this.$q = $q;
};
Service.prototype.getTileCount = 0;
Service.prototype.getTileData = function(Url){
var deferred = this.$q.defer();
this.$http.get(Url)
.success(function(response){
Service.prototype.getTileCount = response.data.length;
console.log('data length :', Service.prototype.getTileCount);
deferred.resolve(response);
});
return deferred.promise;
};
This is component 1 controller code
function Component1Controller(Service) {
this.tileData ={};
var self = this;
var promise = Service.getTileData(this.sourceUrl);
promise.then(function(data) {
self.tileData = data;
Service.getTileCount = data.length;
console.log('This is tileData : '+ Service.getTileCount);
});
};
This is component 2 controller code
function Component2Controller(Service) {
var self = this;
console.log(Service.getTileCount);
// getting getTileCount = 0; After setting timeout function of 5 second I am able to get getTileCount value
};
The thing is that Service.getTileCount is updated asynchronously, that's why it's 0 at first and then at some point it changes. I would recommend you to simplify your service and always work with getTileData method, which would be a single source of data. The implementation would also become simpler:
function Service($http, $q) {
this._tileData = null;
this.$http = $http;
this.$q = $q;
}
Service.prototype.getTileData = function(Url) {
if (!this._tileData) {
this._tileData = this.$http.get(Url).then(function(response) {
return response.data;
}.bind(this));
}
return this._tileData;
};
Note, how it caches tiles response in "private" _tileData property. Now you can always rely on getTileData method which will return data no matter when you call it:
function Component1Controller(Service) {
this.tileData = {};
var self = this;
Service.getTileData(this.sourceUrl).then(function(data) {
self.tileData = data;
console.log('This is tileData:', self.tileData.length);
});
};
function Component2Controller(Service) {
var self = this;
Service.getTileData(this.sourceUrl).then(function(data) {
console.log('tile count', data.length);
});
};
In this case Service.getTileCount is not needed anymore.
Demo: http://plnkr.co/edit/3zE6VL4emXaLRx2nCRih?p=info

Passing an array from service to controller

I cannot seem to figure out how to pass an array from service to a controller.
I have a simple service
.service('test', function() {
var array = []
return array;
})
And a controller where I call this function when a button is pressed
$scope.testArray = function() {
$scope.test = test.array;
console.log("test: ", $scope.test);
};
I get an error test is undefined. Can anyone explain to me please why this doesn't work and how to fix it? I tried storing that array in a separate object but no luck either. THanks
(See also: this SO question about Angular providers)
A service should put properties directly on this. So instead of
.service('test', function() {
var array = [];
return array;
})
try
.service('test', function() {
this.array = [];
})
(code style notwithstanding; many would suggest preferring function access over direct object access)
.service('test', function() {
var array = [];
this.getArray = function(){
return array;
};
})
Just change test.arraywith test:
JSFiddle
.controller('youCtrl', ['$scope', 'test', function ($scope, test) {
$scope.testArray = function() {
$scope.test = test;
console.log("test: ", $scope.test);
};
});
Add the array variable to your service.
angular.module('moduleName').service('test', function() {
this.array = [];
});
Inject your service into your controller.
angular.module('moduleName').controller('controllerName', function(test) {
$scope.test = test.array;
console.log("test: ", $scope.test);
});

How to create unit-testing for array function in AngularJS with Jasmine

I have xf array: var xf = [];
And I have a function is a element in this array and a function to use it:
$scope.checkEmailValid = function () {
var result = false;
Iif (xf.validateEmail($scope.email, '256')) {
result = true;
}
return result;
};
xf.validateUsername = function (sText) {
var isValid = false;
do {
//Check for valid string.
isValid = typeof sText === 'string';
if (!isValid) {
break;
}
//Check that each special character does not exist in string.
for (var i = 0; i < sText.length; i++) {
if (xf.SPECIAL_CHARS.indexOf(sText.charAt(i)) !== -1) {
isValid = false;
break;
}
}
if (!isValid) {
break;
}
} while (false);
return isValid;
};
But when I run my spec:
it ('checkEmail', function(){
$controller('MyCtrl', { $scope: $scope });
xf.validateUsername();
spyOn(window,xf.validateUsername).and.callThrough();
});
It makes an error:
xf.validateUsername is not a function
How can I cover it?
The xf variable is not acessible from the outside of the controller's scope (i.e. not accessible in the unit test files).
You must've done the following thing:
angular
.module('myModule')
.controller(function ($scope) {
var xf = [];
// etc.
});
You could attach the xf variable to the MyController instance once Angular instantiates it:
angular
.module('myModule')
.controller(function ($scope) {
this.xf = [];
// etc.
});
But that's not really a clean way of doing it. A better way (in my opinion) would be to create a factory:
angular
.module('myModule')
.factory('xfService', function () {
var xf = [];
function validateUsername(text) {
// etc.
}
function get() {
return xf;
}
return {
get: get,
validateUsername: validateUsername
};
});
Now, you can inject the factory in your controller to use xf:
angular
.module('myModule')
.controller(function ($scope, xfService) {
// somewhere
var isValid = xfService.validateEmail($scope.email, '256');
// to get the values in the array
var values = xfService.get();
});
Finally, for the unit tests, it becomes really easy to test the validateEmail method.
describe('Unit tests - xfService', function () {
var xfService;
beforeEach(angular.module('myModule'));
beforeEach(angular.inject(function (_xfService_) {
xfService = _xfService_;
});
});
describe('xfService.validateUsername', function () {
it('should return a boolean value', function () {
// for example
expect(typeof xfService.validateUsername('test')).toBe('boolean');
});
// add more unit tests to check that the method works as expected
});
});
You'll need to add the angular-mocks file to the Karma config.
Thanks to Paul Podlech and Claies for the hints in the comments/answers.
I'm not sure to completely understand your question. But there are a few thinks i think you are doing wrong:
If xf it's a global variable you should mock it, since you are testing the controller, not the global variable.
If you want to check the real function of your global variable, go to the karma.conf.js file and add the js file path to the files option:
files: [
...,
'fx-script.js',
...
],
callThrough should be used before the actual function is invoked:
it ('checkEmail', function(){
var ctrl = $controller('MyCtrl', { $scope: $scope });
spyOn(window, ctrl.xf.validateUsername).and.callThrough();
ctrl.xf.validateUsername();
});
I recommend you to separately test your controller, service, or global scripts, and add mocks whenever you need to inject a dependency or global variable, so if you can tell for sure which module/script is failing any time.
you should move functionality in xf into separate service/factory. Then inject it in controller. That makes it pretty easy to mock it while testing.
Try this in the controller
var xf = this.xf = [];
and this in your test
it ('checkEmail', function(){
var xf = $controller('MyCtrl', { $scope: $scope }).xf;
spyOn(xf, 'validateUsername').and.callThrough();
xf.validateUsername();
});
But you should realize that this exposes your xf object on the Controller as mentioned in the comment of Claies.

Calling a service from within another service in AngularJS

I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).

Categories