Testing nested object with jasmine - javascript

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.

Related

Jasmine test private callback function

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]*$/));
});

Object method loses its scope when called using setInterval

Is there any way to print out the value of the array players like in the example below? I've tried to find a solution for hours now...
function Room(name, id, owner) {
this.players = [];
this.movementz = function() {
console.log(this.players);
}
}
I'm calling the function using setInterval, like this:
setInterval(room.movementz, 1000);
The problem here is about the this object: creating your object and manually calling it's movementz method will work because the this element is the object itself, but using setInterval will cause the method to be called with this === window.
Here is an example:
var room = new Room();
room.movementz(); // []
setInterval(room.movementz, 1000); // undefined
This happens because when the movementz method gets called by setInterval, the this object is window, so, to fix this, you'll have to force the function to be called using room as this. You can easily accomplish this using the bind method, here's an example:
var room = new Room(),
players = "hello";
setInterval(room.movementz, 1000);
// this will output "hello" because this === window
setInterval(room.movementz.bind(room), 1000);
// this will output [], because now this === room

need help understanding closures usage in this code

Here is a simplified snippet from some code I wrote for managing tablet gestures on canvas elements
first a function that accepts an element and a dictionary of callbacks and register the events plus adding other features like 'hold' gestures:
function registerStageGestures(stage, callbacks, recieverArg) {
stage.inhold = false;
stage.timer = null;
var touchduration = 1000;
var reciever = recieverArg || window;
stage.onLongTouch = function(e) {
if (stage.timer) clearTimeout(stage.timer);
stage.inhold = true;
if (callbacks.touchholdstart) callbacks.touchholdstart.call(reciever, e);
};
stage.getContent().addEventListener('touchstart', function(e) {
e.preventDefault();
calcTouchEventData(e);
stage.timer = setTimeout(function() {
stage.onLongTouch(e);
}, touchduration);
if (callbacks.touchstart) callbacks.touchholdstart.call(reciever, e);
});
stage.getContent().addEventListener('touchmove', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdmove) callbacks.touchholdmove.call(reciever, e);
} else {
if (callbacks.touchmove) callbacks.touchmove.call(reciever, e);
}
});
stage.getContent().addEventListener('touchend', function(e) {
e.preventDefault();
if (stage.timer) clearTimeout(stage.timer);
if (stage.inhold) {
if (callbacks.touchholdend) callbacks.touchholdend.call(reciever, e);
} else {
if (callbacks.touchend) callbacks.touchend.call(reciever, e);
}
stage.inhold = false;
});
}
later I call registerStageGestures on a few elements (represented by 'View' objects) in the same page. Something like:
function View() {
var self=this;
..
function InitView() {
...
registerStageGestures(kineticStage, {
touchstart: function(e) {
// do something
},
touchmove: function(e) {
// do something
},
touchendunction(e) {
// do something
},
touchholdstart: function(e) {
// do something
},
touchholdmove: function(e) {
// do something
},
touchholdend: function(e) {
// do something
},
}, self);
Everything works fine, however I'm left wondering about two things in the implementation of registerStageGestures:
First, is it necessary to make inhold, timer and onLongTouch members of the stage ? or will closures make everything works well if they are local vars in registerStageGestures ?
Second, is it necessary to call the callbacks with '.call(receiver,' syntax ? I'm doing this to make sure the callback code will run in the context of the View but I'm not sure if it's needed ?
any input is much appreciated
Thanks!
First, is it necessary to make inhold, timer and onLongTouch members
of the stage ? or will closures make everything works well if they are
local vars in registerStageGestures ?
As far as registerStageGestures() is concerned, var inhold, var timer and function onLongTouch(e) {...}. would suffice. The mechanism by which an inner function has automatic access to its outer function's members is known as "closure". You would only need to set stage.inhold, stage.timer and stage.onLongTouch if some other piece of code needs access to these settings as properties of stage.
Second, is it necessary to call the callbacks with '.call(receiver,'
syntax ? I'm doing this to make sure the callback code will run in the
context of the View but I'm not sure if it's needed ?
Possibly, depending on how those callbacks are written. .call() and .apply() are sometimes used when calling functions that use this internally. In both cases, the first parameter passed defines the object to be interpreted as this. Thus, javascript gives you the means of defining general purpose methods with no a priori assumption about the object to which those methods will apply when called. Similarly, you can call a method of an object in such a way that it acts on another object.
EDIT:
For completeness, please note that even in the absence of this in a function, .apply() can be very useful as it allows multiple parameters to be specified as elements of a single array, eg the ubiquitous jQuery.when.apply(null, arrayOfPromises)...
There are some simple answers, here.
First, closure:
Closure basically says that whatever is defined inside of a function, has access to the rest of that function's contents.
And all of those contents are guaranteed to stay alive (out of the trash), until there are no more objects left, which ere created inside.
A simple test:
var testClosure = function () {
var name = "Bob",
recallName = function () { return name; };
return { getName : recallName };
};
var test = testClosure();
console.log(test.getName()); // Bob
So anything that was created inside can be accessed by any function which was also created inside (or created inside of a function created in a function[, ...], inside).
var closure_2x = function () {
var name = "Bob",
innerScope = function () {
console.log(name);
return function () {
console.log("Still " + name);
}
};
return innerScope;
};
var inner_func = closure_2x();
var even_deeper = inner_func(); // "Bob"
even_deeper(); // "Still Bob"
This applies not only to variables/objects/functions created inside, but also to function arguments passed inside.
The arguments have no access to the inner-workings(unless passed to methods/callbacks), but the inner-workings will remember the arguments.
So as long as your functions are being created in the same scope as your values (or a child-scope), there's access.
.call is trickier.
You know what it does (replaces this inside of the function with the object you pass it)...
...but why and when, in this case are harder.
var Person = function (name, age) {
this.age = age;
this.getAge = function () {
return this.age;
};
};
var bob = new Person("Bob", 32);
This looks pretty normal.
Honestly, this could look a lot like Java or C# with a couple of tweaks.
bob.getAge(); // 32
Works like Java or C#, too.
doSomething.then(bob.getAge);
? Buh ?
We've now passed Bob's method into a function, as a function, all by itself.
var doug = { age : 28 };
doug.getAge = bob.getAge;
Now we've given doug a reference to directly use bobs methid -- not a copy, but a pointer to the actual method.
doug.getAge(); // 28
Well, that's odd.
What about what came out of passing it in as a callback?
var test = bob.getAge;
test(); // undefined
The reason for this, is, as you said, about context...
But the specific reason is because this inside of a function in JS isn't pre-compiled, or stored...
this is worked out on the fly, every time the function is called.
If you call
obj.method();
this === obj;
If you call
a.b.c.d();
this === a.b.c;
If you call
var test = bob.getAge;
test();
...?
this is equal to window.
In "strict mode" this doesn't happen (you get errors really quickly).
test.call(bob); //32
Balance restored!
Mostly...
There are still a few catches.
var outerScope = function () {
console.log(this.age);
var inner = function () {
console.log("Still " + this.age);
};
inner();
};
outerScope.call(bob);
// "32"
// "Still undefined"
This makes sense, when you think about it...
We know that if a function figures out this at the moment it's called -- scope has nothing to do with it...
...and we didn't add inner to an object...
this.inner = inner;
this.inner();
would have worked just fine (but now you just messed with an external object)...
So inner saw this as window.
The solution would either be to use .call, or .apply, or to use function-scoping and/or closure
var person = this,
inner = function () { console.log(person.age); };
The rabbit hole goes deeper, but my phone is dying...

How to _really_ delete/clear/forget a object instance in javascript?

Im wondering how to properly "Clear" a object instance.
With the code below, the internal setInterval() will continue to run even after the instance is "cleared" by the parent.
// simple Class
var aClass = function(){
return {
init: function(){
console.log("aClass init()")
setInterval( this.tick, 1000 );
// note: we're not storing the ref
},
tick: function(){
console.log("aClass tick");
}
}
}
// instantiate the class
var inst = new aClass();
inst.init();
// try to forget the instance
function test(){
console.log("test() 1 inst:", inst);
inst = null;
console.log("test() 2 inst:", inst);
}
// run for a while, then call test()
setTimeout( test, 4000 );
Output:
aClass init()
aClass tick
aClass tick
aClass tick
test() 1 inst: {.....}
test() 2 inst: null
aClass tick
aClass tick ...
Problem is that the "aClass tick" message continues to print after the test().
Ideas?
Your instance is being kept in memory because the function you passed to setInterval is being kept in memory and has a reference to it. That function is referenced by the browser's list of active interval timers.
You'll need to remember the interval handle:
this.intervalHandle = setInterval( this.tick, 1000 );
...then later when you're dropping your reference to inst, you'll want to tell it that:
inst.cleanup();
inst = null;
...and in inst.cleanup:
clearInterval(this.intervalHandle);
That way, the browser will remove its reference to the function you passed to setInterval, which means that function will become eligible for garbage collection (based on your code there are no other references to it). And that means the reference it has to your instance is released, and so if no other outstanding references to that instance exist, it's eligible for garbage collection.
You should use clearInterval() rather than try to delete the reference.
This has been answered before - see
Stop setInterval call in JavaScript
That's because the browser itself keeps track of all the functions scheduled with setInterval and setTimeout. (Otherwise you'd have to store the function yourself somewhere, which in most cases would be pretty annoying.) And your function has a reference to a method, so that method stays alive. But I suspect the rest of the object would have been thrown away, though that's a bit difficult to prove.
If you ever plan to unschedule such a function, you need to do so explicitly:
this.interval = setInterval(function, 4000);
// and then later, in some `destroy()` method
clearInterval(this.interval);
And, by the way, you should very rarely need to return a big hash like that from a constructor! Work with the prototype instead:
var SomeClass = function() {
// ...
};
SomeClass.prototype.method = function() {
console.log('hello from', this);
};
My guess is that your instance of aClass is gone since there is no way to access any of it's properties but in init the tick function is copied when used as a parameter to setinterval.
var tickfunction=null;
var aClass = function(){
return {
init: function(){
console.log("aClass init()")
tickfunction = this.tick;
},
tick: function(){
console.log("aClass tick");
}
}
}
// instantiate the class
var inst = new aClass();
inst.init();
// try to forget the instance
console.log("test() 1 inst:", inst);
inst = null;
console.log("test() 2 inst:", inst);// its null isn't it?
console.log("the tickfunction:",tickfunction);
To illustrate that the function is copied:
function toInterval(){
console.log("orig");
}
setInterval(toInterval,1000);//prints orig every second
toInterval=function(){
console.log("changed it");
}
toInterval();//prints changed it

Reaching an Object's Property

In my application I have an object with several properties that get set in various places in the application.
In one of my prototype functions I have a function that runs in intervals to update a timer, and in that function the property (this.)theTime should be set. The problem is that this doesn't happen, and I guess the reason is that this.theTime points to the function itself instead of the object.
Below is two versions of my code, and neither of them works. Any tips for me?
// 1.
function changeTime() {
this.theTime = setTime(time);
time.setSeconds(time.getSeconds()+1);
p1.html(this.theTime);
}
interval = setInterval(changeTime(), 1000 );
// 2.
function changeTime(theTime) {
theTime = setTime(time);
time.setSeconds(time.getSeconds()+1);
p1.html(theTime);
}
interval = setInterval( function() { changeTime(this.theTime); }, 1000 );
...
Too make it more clear, the function above updates a timer (eg. 00:00:01 -> 00:00:02) every second, and I want this.theTime to be updated with the time.
When the timer stops (which happens in another prototype function) I want to be able to see what time the timer stopped on, but as it is now this.theTime is the default value, which means that the function above doesn't update the objects property. Instead this.theTime in the function above must be a local variable.
NOTE: setTime() is another function that exists in the same prototype function as the function above.
Well when you use this in some function this is referencing to the object which actually the function is. Here:
function myF() {
this.var = 'hey';
}
You can reach var using this (myF as a constructor function):
var obj = new myF();
alert(obj.var);
Or here:
function myF2() {
if (typeof this.var === 'undefined') {
this.var = 0;
} else {
this.var += 1;
}
alert(this.var);
}
Here var again is a property of myF2 (which as I said is not just a function because in JavaScript functions are objects).
Each time you call myF2 this.var is going to be incremented and alerted (just in the first call it's going to be initialized).
In the second function (anonymous function using in the second setInterval) you're doing the same.
One solution is to make theTime global in both cases so you don't need to use:
this.theTime
So the result can be something like this:
var theTime = 0, interval;
function changeTime() {
theTime += 1;
document.body.innerHTML = theTime;
setInterval
}
interval = setInterval(changeTime, 1000 );
http://jsfiddle.net/u3EuC/
You can verify easily by writting a
debugger;
to set a breakpoint in your functions. Then it may be pretty easy to find your problem.
You are correct in your assumption that there's something wrong with your this keyword. this in JavaScript is a bit tricky, so using it in functions (especially with setTimeout or setInterval is risky.
What you want to do is save the value of this when you create the function.
Here's more information: http://justin.harmonize.fm/index.php/2009/09/an-introduction-to-javascripts-this/
Maybe these comments will direct you to the right way
var theTime; // global variable
function changeTime() {
theTime = setTime(time); // theTime is global variable declared above (accesible from anywhere)
// var myTime = setTime(time); // myTime is local variable
time.setSeconds(time.getSeconds()+1);
p1.html(theTime);
}
interval = setInterval(changeTime, 1000 ); // no braces
Jason, after your clarification, I believe it is better to provide you whole new answer trying to explain this statement in JS as good (and simple) as possible. I hope it helps.
<html>
<body>
<div id="output1"></div>
<div id="output2"></div>
<script>
// theTime is undefined in global scope
function obj(target) {
var theTime = 0;
var that = this; // var means "private"
this.changeTime = function() { // here "this" points to obj and means "public"
theTime++; // no var => outer scope = obj scope
// here "this" points to changeTime function, not to obj!
// "that" points to obj, you may use that.theTime
document.getElementById(target).innerHTML = theTime;
}
}
var o1 = new obj("output1");
var o2 = new obj("output2");
setInterval(o1.changeTime,1000); // update output1 content every second
setInterval(o2.changeTime,500); // update output2 content twice a second
</script>
</body>
</html>

Categories

Resources