I have a class where I bind a method on initialization as follows -
function MyClass() {
this.onHttpCallback = _.bind(onHttpCallback, this);
}
function onHttpCallback(){
//...
}
How do I test if the onHttpCallback when called, is always called with an object of MyClass as context?
I am using sinon.js to mock and the following code doesn't work -
it('should be binded', function () {
//ctrl is an object of MyClass
var dummy_obj = {};
var spy = sinon.spy(ctrl.onHttpCallback);
spy.call(dummy_obj);
spy.alwaysCalledOn(ctrl).should.be.ok;
});
Update
As per the comments in the following answer, it seems like it is impossible to test the binding for a method.
My Take on the problem
//Source.js
function MyClass() {
}
MyClass.prototype.init = function(){
this.onHttpCallback = _.bind(MyClass.onHttpCallback, this);
}
MyClass.onHttpCallback(){
//...
}
//Test.js
it('should bind onHttpCallback', function () {
sinon.spy(_, 'bind');
ctrl.init();
_.bind.calledWith(ctrl.constructor.onHttpCallback, ctrl).should.be.ok;
_.bind.restore();
});
Works like a charm!
In case you wonder why this changes even though you clearly bound it to be MyClass before, that's because you use call with dummy_obj on the spy.
The spy wraps the original function, so it has no concept of that function's binding. It will still accept a different binding on the wrapper function, then try to call the original with that this, which is then ignored by the original function.
var context = {foo: 'bar'};
var original = _.bind(function () { console.log(this); }, context);
var spy = function (original) {
var spyFn = function () {
var _this = this;
spyFn.calledOn = function (ctx) { return ctx === _this; };
return original.apply(this, arguments);
};
return spyFn;
};
var originalSpy = spy(original);
// Will call the spyFn with a different `this`, but will not affect the original binding
originalSpy.call({something: 'else'});
>> {foo: 'bar'}
// Since the spy cannot know the original binding, it will think the assumption is false.
originalSpy.calledOn(context) === false;
Related
On the simple example below and on JSFiddle here - https://jsfiddle.net/jasondavis/dnLzytju/ you can see the issue I have.
I can see why it could happen but I am not sure how to fix it while keeping the same JS structure.
The issue is when I define a JavaScript objects prototype functions and I have a 2nd level nested object which has a function and in that function I call a function on the parent/root level it fails.
This function from the code below this.nestedObject.nested_object_function() tries to call the function this.normal_function() however it fails and says:
Uncaught TypeError: this.normal_function is not a function
at Object.nested_object_function (VM2493:79)
I assume the reason is that this is referencing this.nestedObject instead of the parent object.
If that is the case, then how can I call that function like I am trying to do from the nested object function and call a parent function?
I have also tried calling JsLibTest.normal_function() as a test from the this.nestedObject.nested_object_function() function but I get the same error.
var JsLibTest = (function (document) {
"use strict";
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
};
/**
* JsLibTest prototype functions
*/
JsLibTest.prototype = {
init: function() {
// as expected this function runs fine
this.normal_function();
// nested level objects functions run fune from parent level object function
this.nestedObject.nested_object_function();
},
normal_function: function() {
console.log('this.normal_function() ran');
},
nestedObject: {
// calling a function on the parent object fails here when called from this nested object function
nested_object_function: function() {
this.normal_function();
console.log('this.nestedObject.nested_object_function() ran');
},
}
};
return JsLibTest;
})(document);
// run it
$(document).ready(function(){
var Sidebar2 = new JsLibTest();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Your assessment is correct. this will be set to the nested object instead of the parent object and that's why it says the function is undefined.
What you need is a way of referencing the parent. Objects don't normally carry any information needed to reference an object which references them. This makes sense when you consider the fact that many objects can reference the same object internally.
You can either store a reference to the parent object and reference that in the nested function:
var nested = {
g() {
this.parent.f();
}
};
var parent = {
f() {
console.log('called');
}
};
nested.parent = parent;
nested.g();
or you can use Function.prototype.call (or something similar) to set the correct context.
var obj = {
f() {
console.log('called');
},
g() {
this.nested.nested_f.call(this);
},
nested: {
nested_f() {
this.f();
}
}
};
obj.g();
Putting the last solution in to the context of your problem:
var JsLibTest = (function(document) {
"use strict";
var JsLibTest = function() {
this.init();
};
JsLibTest.prototype = {
init: function() {
this.normal_function();
// NOTICE: Using .call here to set the context
this.nestedObject.nested_object_function.call(this);
},
normal_function: function() {
console.log('this.normal_function() ran');
},
nestedObject: {
nested_object_function: function() {
this.normal_function();
console.log('this.nestedObject.nested_object_function() ran');
}
}
};
return JsLibTest;
})(document);
// run it
$(document).ready(function() {
var Sidebar2 = new JsLibTest();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You are correct that scope doesn't have access to the parent. Easy solution would be that you pass parent to the nested object like:
this.nestedObject.nested_object_function(this);
then in your nested function call parent as:
nested_object_function: function(self) {
self.normal_function();
alert('this.nestedObject.nested_object_function() ran');
}
since you pass this (parent) as self you can then call it from nested one.
At first, the Object must be unique for Each, having a prototype:
this.nestedObject=Object.create(this.nestedObject);
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
//bind to childs:
this.nestedObject.parent=this;
};
Now you can use this.parent inside of your inner function...
this.parent.normal_function();
If you want this to be the parent, bind:
var JsLibTest = function (){
// run init() function on initiation of a new JsLibTest object
this.init();
//bind to childs:
for(i in this.nestedObject){
var el=this.nestedObject[i];
if(typeof el==="function"){
this.nestedObject[i]=el.bind(this);
}
}
};
To make it easier, may use sth like that ( a helper function):
getfunc:function(...a){
a.reduce((obj,key)=>obj[key],this).bind(this);
}
Use like this:
JsLibTestInstance("nestedObject","nestedobject_function")();
Yea, you are right that the this value in your JSLibTest.prototype.nestedObject function is pointing to nestedObject and not JSLibTest.
If you want to maintain the same call signature, you can declare nestedObject as an IIFE:
nestedObject: (function() {
var that = this;
return {
nested_object_function: function() {
console.log(that);
// this.normal_function();
alert('this.nestedObject.nested_object_function() ran');
}
}
}())
https://jsfiddle.net/dnLzytju/1/
Note: You probably do not want to declare your prototype that way is it effectively deletes all the native prototype methods of the object.
To author your code in a similar way, consider using Object.assign to help you out.
var foo = Object.assign({}, Function.prototype, {
bar() {
console.log("Hello!")
}
});
foo.bar();
I have read this answer and IIFE but I can't seem to find the correct solution to my problem.
I have a simple class here:
define(['jquery'], function($) {
// Need 'self' because someCallback() is being called with .call() and 'this' changes
var self;
function Foo(number) {
self = this;
this.someNumber = number;
}
Foo.prototype = {
someCallback: function () {
//Use self because 'this' changes to a DOM element
var num = self.someNumber;
//Do something with the num
return num * 2;
}
};
return Foo;
});
and someCallBack() is being called by a jQuery plugin using .call(). Because of this, the context changed, hence the use of the self variable.
However, this is wrong because:
define(['foo'], function(Foo) {
describe('context question', function () {
var foo1 = new Foo(1);
var foo2 = new Foo(2);
it('"this" should work', function () {
var call1 = foo1.someCallback.call(this); // 4
var call2 = foo2.someCallback.call(this); // 4
expect(call2).toBe(4); // Only works because it is 'new' last
expect(call1).toBe(2); // Fails because 'self' is taken from foo2
});
});
});
How exactly should I wrap the self variable to make this code work?
You could probably just use the revealing module pattern and declare it as a "global" variable (local to the module):
define(['jquery'], function($) {
var someNumber;
function Foo(number) {
someNumber = number;
}
Foo.prototype = {
someCallback: function () {
return someNumber * 2;
}
};
return Foo;
});
Two ways of calling an object method which stores its own this value include
Define the method as a nested function which references its this value in a closure which stores this value in a variable. The function defined could be anonymous or declared with a name but must be evaluated each time a class instance is created, so as to create a new Function object capturing different values of self in function scope.
Take a statically defined function object and bind its this value using bind. Bind creates a new wrapper function object each time it is called.
The first method looks like (without Jquery or Jasmine):
function Foo(number)
{ var self = this;
this.num = number;
this.someCallback = function() // method with new Foo object stored as self in function scope
{ // something with num:
return self.num * 2;
}
}
and the second method could look like
function Foo(number)
{ this.num = number
this.someCallback = this.someCallback.bind(this); // bind prototypical method as local method.
}
Foo.prototype = {
someCallback: function () {
// this value is bound by constructor;
//Do something with the num
return this.num * 2;
}
};
I have a function
var data = {};
var myFunc = function() {
data.stuff = new ClassName().doA().doB().doC();
};
I'd like to test that doA, doB, and doC were all called.
I tried spying on the instance methods like this
beforeEach(function() {
spyOn(ClassName, 'doA');
};
it('should call doA', function() {
myFunc();
expect(ClassName.doA).toHaveBeenCalled();
});
but that just gives me a "doA() method does not exist" error.
Any ideas?
Where you went wrong was your understanding of how to refer to methods in JavaScript in a static context. What your code is actually doing is spying on ClassName.doA (that is, the function attached to the ClassName constructor as the property doA, which is not what you want).
If you want to detect when that method gets called on any instance of ClassName anywhere, you need to spy on the prototype.
beforeEach(function() {
spyOn(ClassName.prototype, 'doA');
});
it('should call doA', function() {
myFunc();
expect(ClassName.prototype.doA).toHaveBeenCalled();
});
Of course, this is assuming that doA lives in the prototype chain. If it's an own-property, then there is no technique that you can use without being able to refer to the anonymous object in myFunc. If you had access to the ClassName instance inside myFunc, that would be ideal, since you could just spyOn that object directly.
P.S. You should really put "Jasmine" in the title.
Let’s do some code refactoring as we want implement constructor injection pattern as James Shore mentions that:
Dependency injection means giving an object its own instance variables. Really. That’s it.
var data = {};
var stuff = new ClassName()
var myFunc = function(stuff) { // move step of creation new object outside
data.stuff = stuff.doA().doB().doC();
};
And time for some tests
function ClassName() {
}
var data = {};
var stuff = new ClassName()
var myFunc = function(stuff) {
data.stuff = stuff.doA().doB().doC();
};
describe('stub for ClassName implementation', function() {
var stubStuff = {
doA: function() {
return stubStuff
},
doB: function() {
return stubStuff
},
doC: function() {
return stubStuff
}
}
beforeEach(function() {
spyOn(stubStuff, 'doA').and.callThrough();
});
it('calls "doA" on "myFunc" exection', function() {
myFunc(stubStuff);
expect(stubStuff.doA).toHaveBeenCalled();
});
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
I have a class-like function
var myapp = function() {
this.method = function() {
//Do something...
}
}
To reference myapp from within methods, the first line in the myapp function is
var self = this;
So a method in myapp can reference the "class" safely
this.anothermethod = function() {
self.method();
}
The full code:
var myapp = function() {
var self = this;
this.dosomething = function(Callback) {
Callback();
}
this.anothermethod = function() {
//Pass a callback ("self" is required here)...
this.dosomething(function() {
self.complete();
)};
}
this.complete = function() {
console.log('All done!');
}
}
My question is: can I assign var self = this; from outside the declaration of myapp? I don't want to set self every single time I write a "class".
Kind of like this:
var library = function() {
this.loadclass = function(Name) {
var tempclass = window[Name];
library[Name] = new tempclass();
library[Name].self = library[Name];
}
}
var myapp = new library();
myapp.loadclass('myapp');
myapp.myapp.dosomething();
It doesn't work as expected. self equals window for some reason.
I know it's a little abnormal programming, but can it be done?
Note about using self: I remember why I started using it. I wanted to reference the base class (this) from within callbacks inside methods. As soon as you try to use this within a function within a method, it then references the method, not the base class.
Unless you are detaching the methods from the object and calling them as plain functions, you don't need a self variable at all. The method can reach its object using the this keyword:
var myapp = function() {
this.method = function() {
//Do something...
}
this.anothermethod = function() {
this.method();
}
}
No, you can't really; not the way you're creating objects at least.
You can sort of do this, by enumerating all the functions on the object and binding them to the object itself. Something like this:
Object.keys(obj)
.filter(function(n) { return typeof obj[n] == "function" })
.forEach(function(n) { obj[n] = obj[n].bind(obj) })
This function will go over the public, enumerable properties of obj and make sure that any functions on it are bound to obj; i.e. this is now bound to obj.
A primer on this
When you call new, this within the constructor gets bound to the newly created object. If you do need a reference to this as it was bound at constructor time, you do need to keep away a reference to it.
Functions in JavaScript are bound to wherever it is called. Here's an example:
var foo = new function() {
this.bar = function() {
return 'bar'
}
this.baz = function() {
return this.bar()
}
}
console.log(foo.bar()) // bar
console.log(foo.baz()) // bar
var bar = function() {
return "window"
}
var baz = foo.baz
console.log(baz()) // window
When we call foo.baz() it'll look to foo for the implementation of bar, but when calling foo.baz through a "detached" reference, it'll look to whatever the global object is (in this case the browser window object) and call bar from there. Because we defined bar in the global context, it then returns window.
The practice of assign a variable called self is so that it doesn't matter how you call your methods, because you always reference the this at the time of creation through the self variable. You don't have to write things this way, but then you should understand that references to this may change under your feet.
I am a little bit confused on how this works, let me post you an example code:
someClass = function() {
this.a = 10;
this.Foo = function() {
console.log(this.a); // will output "10"
setTimeout(this.Bar, 1);
}
this.Bar = function() {
console.log(this.a); // undefined
}
}
var instance = new someClass();
instance.Foo();
My understanding is that this.a is not visible in function Bar if it is called from setTimeout (or some other "handler" type of thing.)
What is the common/correct way of solving that?
(I am trying this in Node.js btw)
Thank you.
The problem is that the scope (this) is lost when you pass a function to setTimeout.
Here's the easiest way to fix it, store this as a reference through a closure and use that instead.
// This uses _me_ everywhere for consistency, but the only place that really needs it
// is within the Bar method. But consistency in this case makes your code more change-
// proof, say if someone calls setTimeout(instance.Foo, 1)
someClass = function() {
var me = this;
me.a = 10;
me.Foo = function() {
console.log(me.a); // will output "10"
// setTimeout will cause me.Bar to be called with window as the context
setTimeout(me.Bar, 1);
}
me.Bar = function() {
// so we avoid using _this_ in here
console.log(me.a); // 10
}
}
A slightly more elegant way is to use Function.bind
someClass = function() {
this.a = 10;
this.Foo = function() {
console.log(this.a); // will output "10"
// the bind call will force it to use the first argument as `this`
setTimeout(this.Bar.bind(this), 1);
}
this.Bar = function() {
console.log(this.a); // undefined
}
}
When passing the function this.Bar as an argument to another function, you need to bind this.Bar to the context you'd like it to be executed with.
If you're using a JS library like jQuery or Underscore.js they already come with that functionality:
setTimeout(_.bind(this.Bar, this), 1);
Here's a simple implementation of a bind function:
var bind = function(scope, fn) {
return function() {
return fn.apply(scope, arguments);
};
}
Update:
As #generalhenry pointed out, node.js already comes with a bind function in (Function.prototype.bind), so you can do this without adding a custom bind function nor an external library:
setTimeout(this.Bar.bind(this), 1);