I know that calling private functions directly in unitTests is not a good practice and we must test the private code trough public methods.
I'm in a case that I don't know what to do to achieve what I want. I want to know if a callback function has been called from my interval. This is implemented in an angular controller.
function prepareInterval() {
self.callbacksData = [];
if(self.DynamicValuesList !== null) {
self.myPromise = $interval(callbackFunction, userInputInterval * 1000);
}
}
and my callback function only shows the data from the callbackFunction. I want to unitTest if that callbackFunction has been called but I can't.
I tried https://makandracards.com/makandra/32477-testing-settimeout-and-setinterval-with-jasmine
it('myUnitTest', function(){
//Prepare data
var controller = createController();
spyOn(controller, 'callbackFunction');
expect(controller.callbackFunction).not.toHaveBeenCalled();
});
The error that I'm getting is.
callbackFunction() method does not exist
EDIT: By the way I'm injecting the angular mock in the beforeEach function
I would take a slightly different approach here.
Obviously you don't want the callbackFunction itself to be exposed, so don't. Keep it private.
You do, however, return it as a value to your self instance.
self.myPromise = $interval(callbackFunction, userInputInterval * 1000);
So what you cán test, is that self.myPromise value. If that value is containing the interval, your interval has been set and thus you can be pretty sure your method has been called.
I expect that self object to be the controller, so you can just test the value of self.myPromise like this:
it('myUnitTest', function(){
//Prepare data
var controller = createController();
expect(controller.myPromise).toBe( /* undefined? */);
});
update
Just to test the interval value > 0:
You can try to refactor for testability. I'm not sure if the stringmatcher works on numbers though, and don't have time for a test myself now :)
function prepareInterval() {
self.callbacksData = [];
if(self.DynamicValuesList !== null) {
self.myPromise = $interval;
self.myPromise(callbackFunction, userInputInterval * 1000);
}
}
it('myUnitTest', function(){
//Prepare data
var controller = createController();
spyOn(controller, 'myPromise');
expect(controller.myPromise).toHaveBeenCalledWith(jasmine.any(Function), jasmine.stringMatching(/^[1-9][0-9]*$/));
});
Related
There is a API call that I am making on the onInit function.
vm.$onInit = function() {
var callInProgress = false;
var resultsLoaded = false;
var url = '/api/times/cst';
if(callInProgress === false && resultsLoaded ===false){
callInProgress = true;
HttpWrapper.send(url,{"operation":'GET'}).then(function(result){
vm.times = result;
resultsLoaded = true;
},function(error){
vm.errorInApi = true;
});
}
Now $onInit is being called multiple times hence the two flags callInProgress, resultsLoaded are being initialized every time.
So, the check is kind of not working.
The API is being called each time the $onInit is called, multiple times on initialization.
How can I make the call only once?
It has be called on $onInit though.
I advice wrapping the API call in a service like:
(function (angular) {
'use strict';
angular
.module('services.common')
.service('TimesService', TimesService);
TimesService.$inject = ['HttpWrapper'];
function TimesService(HttpWrapper) {
var timesService = this;
var timesResult = null;
timesService.getTimes = getTimes;
function getTimes() {
if (!timesResult) {
timesResult = HttpWrapper.send('/api/times/cst', {"operation": 'GET'});
}
return timesResult;
}
return timesService;
}
})(angular);
and then inject it into your controller and use like TimesService.getTimes().then(...), so the call to the API will be done only once on the first call of TimesService.getTimes (since the result would be stored in timesResult inside of the service).
callInProgress and resultsLoaded are within the onInit function and part of the directive controller. They are going to get created each time you use the directive. Using a controller property on the containing controller to do something similar would be the way to go. You can use bindings to specify the controller property you need to keep this somewhat generic.
I am trying to pass data from directive to controller via service, my service looks like this:
angular
.module('App')
.factory('WizardDataService', WizardDataService);
WizardDataService.$inject = [];
function WizardDataService() {
var wizardFormData = {};
var setWizardData = function (newFormData) {
console.log("wizardFormData: " + JSON.stringify(wizardFormData));
wizardFormData = newFormData;
};
var getWizardData = function () {
return wizardFormData;
};
var resetWizardData = function () {
//To be called when the data stored needs to be discarded
wizardFormData = {};
};
return {
setWizardData: setWizardData,
getWizardData: getWizardData,
resetWizardData: resetWizardData
};
}
But when I try to get data from controller it is not resolved (I think it waits digest loop to finish), So I have to use $timeout function in my controller to wait until it is finished, like this:
$timeout(function(){
//any code in here will automatically have an apply run afterwards
vm.getStoredData = WizardDataService.getWizardData();
$scope.$watchCollection(function () {
console.log("getStoredData callback: " + JSON.stringify(vm.getStoredData));
return vm.getStoredData;
}, function () {
});
}, 300);
Despite of the fact that it works, what I am interested in is, if there is a better way to do this, also if this is bug free and the main question, why we use 300 delay and not 100 (for example) for $timeout and if it always will work (maybe for someone it took more time than 300 to get data from the service).
You can return a promise from your service get method. Then in your controller, you can provide a success method to assign the results. Your service would look like this:
function getWizardData() {
var deferred = $q.defer();
$http.get("/myserver/getWizardData")
.then(function (results) {
deferred.resolve(results.data);
}),
function () {
deferred.reject();
}
return deferred.promise;
}
And in your ng-controller you call your service:
wizardService.getWizardData()
.then(function (results) {
$scope.myData = results;
},
function () { });
No timeouts necessary. If your server is RESTFULL, then use $resource and bind directly.
Use angular.copy to replace the data without changing the object reference.
function WizardDataService() {
var wizardFormData = {};
var setWizardData = function (newFormData) {
console.log("wizardFormData: " + JSON.stringify(wizardFormData));
angular.copy(newFormData, wizardFormData);
};
From the Docs:
angular.copy
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
Usage
angular.copy(source, [destination]);
-- AngularJS angular.copy API Reference
This way the object reference remains the same and any clients that have that reference will get updated. There is no need to fetch a new object reference on every update.
I'm quite new to Angular and am trying to understand how everything works. I've been poking around and couldn't find any information on how to do this. So, I've got a service that defines
this.totalCount = 0;
In my controller, my get request retrieves some emails and then executes a function called addMessage for each message it retrieves. The addMessage function is in my service.
The function in my service looks like this:
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
}
Basically, I am trying to increment this.totalCount each time this function is executed so that it will update and then can be displayed in the view. I have it displaying in the view currently, however its number always remains 0.
I've tried the following:
1.
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
this.totalCount++;
}
2.
var count = this.totalcount
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
count++; //and then attempted to display this value in the view but with no luck
}
Any tips would be greatly appreciated.
try this:
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
}
I assume that you're binding the var this way in your controller and your view
Service :
this.totalCount = 0;
this.totalCount++;
Controller :
$scope.totalCount = service.totalCount;
view :
{{totalCount}}
And if you're actually doing it like this, you should face this kind of trouble.
The main problem is that totalCount is a primitive var and doing this.totalCount++ will break the reference. If you want to keep some var you should bind it as a sub-object.
This way :
Service :
this.utils = {};
this.utils.totalCount = 0;
this.utils.totalCount++;
Controller :
//This is the most important part. You bind an object. Then even if you loose the totalCount reference, your object will keep its own reference.
$scope.myServiceUtils = service.utils;
View :
{{myServiceUtils.totalCount}}
Actually in service (it's a matter of taste) i prefer a lot to use the object syntax instead of this (as "this" can be confusing)
This way :
var service = {};
service.utils.totalCount = 0;
service.addItem = function(){
...
}
return service;
Hope that was your issue.
You pass argument to another function which has different scope than your service. It is trick with assigning current object to variable, which is visible from function.
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
that.totalCount++;
}
Should work.
So you assign that variable with current object, which is visible in inner function scope.
In a function addMessage body, this refers to function scope which is new, and there is no compiler error, but messagesList is a null object and totalCount is incremented, but after program leave function, it's not visible in service, because it is in a function scope which isn't assigned to any variable.
To update service variable as it changes in your controller, use $watch.
$scope.$watch(function() {
return messagesService.totalCount;
}, function(new,old){
$scope.totalmessagecount = messagesService.totalCount;
});
First parameter of $watch if function which return observed for change element. Another is standard function to perform operation after update.
Here is my Test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
Here is the code
setTimer: function (boxNum, seconds) {
anotherObject.status.timer.boxNum = boxNum;
anotherObject.status.timer.seconds = seconds;
anotherObject.status.timer.visible = true;
},
Here is the error I am getting
TypeError: Cannot read property 'timer' of undefined
I tried setting the object using anotherObject = {} I tried setting anotherObject.status = {} and lastly tried setting anotherObject.status.timer = {}, however I still get the error. Any ideas, how can I mock the object?
Without knowing how/where 'anotherObject' is constructed I would think that you would need to initialize the 'anotherObject' before you execute the setTimer function in your test.
Do you have an init() or setup() function that exists on 'anotherObject' that would initialize the 'timer' object for you?
Although the method looks like it is just trying to make sure that the method is setting all the corresponding properties.
You could do the following before calling setTimer in your test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
//Initialize the anotherObject
anotherObject.status = { timer : {} }
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
This of course comes with the caveat that you have now defined an 'anotherObject' inside your test using the global scope (since excluding the var on any variable definition in javascript makes it global scope). This could effect other test cases that expect the timer object to be setup a certain way but your test case has now set the timer values to 1 and 15 respectively (could be alot of other values all depending on what the test case is doing).
So to help with this, resetting the 'anotherObject' at the beginning or end of your tests would help with pollution
afterEach(function(){
anotherObject.status = { timer : {} }
})
or
beforeEach(function(){
anotherObject.status = { timer : {} }
})
Of course if you have an init(), create() or setup() function on the 'anotherObject' that could be used it would of course give you more realistic results since the object would be much closer to what it would look like in production.
You are not working on the same "anotherObject" object in both source and test codes.
Each code has it's own object and setting values to one will not set in the other.
I would just like to ask whether I would be able to unit test the code inside ExternalFunction within the document.ready? I have tried many things for a while now and still couldn't work out how, and am at my wits end.
$(document).ready(function () {
var originalExternalFunction = ExternalFunction;
ExternalFunction = function(context, param) {
// trying to unit test the stuff in here!
}
}
I'm unit testing using JsTestDriver. Test declaration is something like TestThisTest.prototype.test_this - function() {};
Thanks in advance.
Since, in your example, ExternalFunction is not declared within the scope of the function, it is global (or at least, in whatever scope it may have been defined in outside ready). You can therefore test it by calling it as a global.
The trouble is, in order to assign the function to ExternalFunction, you have to run ready (which you could run manually, if you need). This means that if you put any other functionality in ready, then no, it is not unit testable. If your example code is an accurate reflection of reality, then I suppose it is kinda testable.
The point of a construct like this, is to hide the inner function. If you don't wish to hide it, then Anon.'s suggestion of defining newExternalFunction in a more accessible scope is what you need.
If your function needs to be a closure using variables from within ready, you could define newExternalFunction thus:
var newExternalFunction;
$(document).ready(function () {
var originalExternalFunction = ExternalFunction;
newExternalFunction = function(context, param) {
// trying to unit test the stuff in here!
}
ExternalFunction = newExternalFunction;
}
You would still need to ensure that ready has run, prior to unit testing, but you wouldn't have to rely on ExternalFunction not being reset to originalExternalFunction.
You could do something like:
function newExternalFunction(context, param) {
//etc.
}
$(document).ready(function () {
var originalExternalFunction = ExternalFunction;
ExternalFunction = newExternalFunction;
}
Then it's relatively straightforward to run your unit tests on newExternalFunction.
Theoretically, you could do something like:
ExternalFunction = function() { }
ExecuteDocumentReady(); // implement a mock on $(document).ready(fn) to store the function, and then execute it here
ExternalFunction(fakeContext, fakeParam);
assert(fakeContext.foo == 12); // or whatever you need it to do
That being said, I'm not sure exactly how to do that in javascript.
You could use a closure to generate your callback function:
// create function to make your "extension" function
function createHookFunction(callback) {
// return a function
return function(context, param) {
var ret;
// // trying to unit test the stuff in here!
if (typeof callback == 'function') {
// if you want to trap the return value from callback,
// ret = callback.apply(...);
callback.apply(this, arguments);
}
return ret;
};
}
// your hook now becomes:
$(document).ready(function() {
ExternalFunction = createHookFunction(ExternalFunction);
});
// and your unit test becomes:
var funcToTest = createHookFunction();
funcToTest(testContext, testParam);
// And, you could even test that the callback itself gets called
function someTest() {
var testContext = {}, testParam='test';
var callbackCalled = false;
var funcToTest = createHookFunction(function(context, param) {
callbackCalled = (context === testContext) && (param === testParam);
});
return (funcToTest(testContext, testParam) == 'Expected Return') && callbackCalled;
}