Good approach to using test spies with dynamic function binding - javascript

I'm using $.proxy(this, 'methodName') to use methods of my object as event handlers for DOM events.
When it comes to testing I'd like to use Jasmine's spyOn to monitor whether the callbacks get fired. However as the listener is attached within my Object's constructor by the time I get to spying on the method it's too late, and the raw, unspied function has already been used by $.proxy.
What are good approaches to tackling this? One thing I've considered is spying on the prototype's methods directly, but I'm worried about the impact this might have on each test being independent of others. Another would be to change how I attach the listeners in my code, but this seems like throwing out the baby with the bathwater.

You can spy on the prototype of object before the test starts. So it will not have any impact of your other tests.
var function A {
$.proxy(this, 'methodName');
}
a.prototype.methodName = function() {
console.log('test');
}
describe('…', function() {
var a;
before(function() {
jasmine.spyOn(a.prototype, 'methodName');
a = new A();
});
it('should…', function() {
});
});

Related

Ember.observer run on init

I'm attempting to build an Ember app without prototype extensions and the Ember docs give examples of how to do this, but they don't include the example of when I want my observer to run on init.
So currently if my code were written like this:
fullNameChanged: function() {
// deal with the change
}.observes('fullName').on('init')
The only example I can find to write it is like this:
Person.reopen({
fullNameChanged: Ember.observer('fullName', function() {
// deal with the change
})
});
So how would I tell this code to run on init?
May be you are looking for this
Person.reopen({
fullNameChanged: Ember.on('init', Ember.observer('fullName', function () {
// deal with the change
}))
});
OR (this won't fire handler if change happens on init, use above on that case)
Person.reopen({
init: function(){
Ember.observer('fullName', function() {
// deal with the change
});
}
});
Alright, this edit for answer the mistakes(?) mentioned below
Well, sometimes it might be necessary to fire the observer on initialization time.
Ember.observer is an Ember namespace method, not part of Ember.Object's prototype. Therefore this.observer never exists, but addObserver() does.
There is no need to invoke the handler, Ember runtime will invoke the handler when the property changes
calling this._super is unnecessary unless it really does matter. In this case, if Person just extends Ember.Object calling super doesn't do anything.
By default, does nothing unless it is overridden during class definition.
It's contextual, and as long as OP didn't specify anything about class definition it's beyond the scope of answering.
Nothing better explains than an example
The accepted answer actually contains five separate mistakes, of varying degrees of severity.
It unnecessarily places setting up the observer in the init hook.
It sets up the observer inside the init hook incorrectly, using Ember.observer instead of this.observer, which won't even work.
It fails to invoke (as opposed to setting up) the handler at init time.
It fails to call init on the superclass.
It unnecessarily uses reopen.
1. No need to set up observer in init hook
You do not need any procedural "call" or "invocation" in an init hook to set up an observer. Either of the two following forms will set them up automatically when the object is instantiated.
fullNameChanged: function() { } . observes('fullName')
observeFullNameChanged: Ember.observer('fullName', this.fullNameChanged.bind(this))
2. Use object.observer for procedural setup of observers.
If you did want to set up the observer procedurally, then you call object.observer, not Ember.observer, which is defined for use as above. Calling Ember.observer procedurally will accomplish nothing; Ember will have no idea of what object the property to observe lies. In this case, it would be this.observer('fullName', ...) (although as mentioned above you actually don't need to do this at all; instead use the approach of point 1).
3. Invoke handler on init
But you also want to invoke the handler at init time. There are three ways:
init: function() { this.fullNameChanged(); /* call super */ }
initFullNameChanged: Ember.on('init', this.fullNameChanged.bind(this))
fullNameChanged: function() { ... }.on('init')
where the third option uses the prototype extensions you don't want.
4. Calling super from init
If you are going to have an init hook, even though it's not needed, you need to call super, or things will break down horribly:
init: function() {
...
this._super.apply(this, arguments);
}
5 No need for reopen
reopen accomplishes nothing here. Just put the above properties into the class definition itself.
Solution
The correct answer to what is the equivalent of
fullNameChanged: function observer() { }.observes('fullName').on('init')
is therefore
fullNameChanged: function() { },
observeFullNameChanged: Ember.observer('fullName', this.fullNameChanged.bind(this)),
initFullNameChanged: Ember.on('init', this.fullNameChanged.bind(this))
It would be equivalent, and possibly more readable, to do this:
initFullNameChanged: Ember.on('init', function() {
// define and execute handler
(function fullNameChanged() { ... }());
// set up obsever
this.observe('fullName, fullNameChanged);
})

Unit testing Backbone.Marionette listeners

I'd like to check what listeners are attached to my Marionette component, for example to the controller:
Example code of the component:
var MyController = Marionette.Controller.extend({
initialize: function () {
this.listenTo(OtherModule, "start", function () {
// something happens here
});
this.listenTo(OtherModule, "stop", function () {
// something happens here
});
})
});
var myController = new MyController();
Example code of the unit test:
describe("MyController", function () {
it("should have 2 listeners registered", function () {
// ?
});
});
I can trigger the events and see if the function I wanted to use was executed with the use of the jasmine's spyOn method, but I'm curious if there's a list of attached events available directly on the component.
How can I check what is my component listening to?
I think you're approaching unit testing in the wrong way - unit tests should check that your object interacts with the outside world in the expected way. They shouldn't be concerned with implementation details (like the exact number of event listeners an Object has).
Having said that, you can use the _listeners (Backbone 1.0.x) or _listeningTo (Backbone 1.1.x) property:
var controller = new MyController;
describe("MyController", function () {
it("should have 2 listeners registered", function () {
expect(Object.keys(controller._listeners).length).toEqual(2)
});
});
Source - Marionette.Controller extends Backbone.Events, which stores listeners in that property.
I wouldn't use this approach in a unit test, but it can be very useful for debugging memory leaks.
When I want to debug this kind of thing I often use a window.MyController = MyController. Then in the console I can save window.MyController and play around with it.
It looks like it will show the objects it's listeningTo but I'm not necessarily seeing what events it's tied to in this fashion. Anyway might be a look see. I'm also using Chrome so Firebug in Mozilla might give better info.

Jasmine spyOn: How to make it work when the spyed function reference is passed?

I'm using jasmine to test my javascript, and I'm finding a hard case to spec. I have this function which is being passed directly to the handler:
filters.find('#per_page').change(checkList.filter);
Now, I'd like to spy on it and see if it's being called when i change that:
describe("when I change the number of items per page", function() {
beforeEach(function()
spyOn(checkList, 'filter');
$('#per_page').val('50').trigger('change');
});
it("filters the results list", function() {
expect(checkList.filter).toHaveBeenCalled();
});
});
This test fails. Suprisingly, if I change my code above to:
filters.find('#per_page').change(function() { checkList.filter(); });
it passes. Is there a way to spy on such functions passed or do I have to resort to the anonymous function case?
Yes, there is, but to do so events have to be bound after you have added the spies on your function. The reason for this is that spies replace the property with an other function (it doesn't change the function itself). In your case, what happened is that you assigned the original function and when you added the spies you replaced the property without changing the callback that was bound.
If the way your application is made you can't add your spies before doing the events binding, than the only it can work is to use anonymous function which call the proper callback.

Javascript Prototype function and SVG setAttribute(onclick)

Trying to use an svg onClick to call a prototype function.
Usually to call a prototype function I would just do this.(functionName) but when I put it into the .setAttribute(onclick, "this.(functionName)") it does not recognise the prototype function. Has anyone had any experience in this?
In case the above wasn't clear heres the basic jist of it...
function myobject(svgShape) {
this.svgshape.setAttribute(onclick, 'this.doSomething()');
}
myobject.prototype.doSomething = function() {
alert("works");
}
Three things that may help:
1) First off, I think you're missing this line from the top of your myobject function:
this.svgshape = svgshape;
I'm assuming that was just an error posting the question and have inserted that below.
2) Normally when you're using Prototype (or any modern library), you don't use strings for callbacks, you use functions. Also, you normally assign handlers using the library's wrapper for addEventListener / attachEvent (observe, in Prototype's case) rather than the old DOM0 attribute thing. So:
function myobject(svgShape) {
this.svgshape = svgshape;
$(this.svgshape).observe('click', this.doSomething); // STILL WRONG, see below
}
myobject.prototype.doSomething = function() {
alert("works");
}
3) But JavaScript doesn't have methods (it doesn't really need them), it just has functions, so the above won't ensure that this (the context of the call) is set correctly. With Prototype you'd use bind to set the context:
function myobject(svgShape) {
this.svgshape = svgshape;
$(this.svgshape).observe('click', this.doSomething.bind(this));
}
myobject.prototype.doSomething = function() {
alert("works");
}
(Or you can use your own closure to do it. The advantage of bind is that the closure is in a very well-controlled environment and so doesn't close over things you don't want kept around.)
Now, I've never done any SVG programming with Prototype, so if observe doesn't work for some reason, you might try directly assigning to the onclick reflected property:
function myobject(svgShape) {
this.svgshape = svgshape;
this.svgshape.onclick = this.doSomething.bind(this);
}
myobject.prototype.doSomething = function() {
alert("works");
}
I'm still using bind there so that this has the correct value.
These posts from my anemic little blog offer more discussion of the above:
Mythical methods
You must remember this
Closures are not complicated

Mocking a javascript method with ScrewUnit

I have a simple validation method as follows
function someMethod {
//some processing
}
I want to unit test this method. What is the simplest way I can mock it. Usually if I have an object as follows:
var someObject = function() {
reload : function() {
//reload logic here
}
}
I can stub someObject and then check if mock(someObject).should_receive("reload").exactly('once')
or something like that. But since this time I just have a method. How do I mock that?
Mocking of code that is non-object oriented is difficult. Misko Hevery wrote about it here: http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability. The predicament is lack of a "hook" through which mocking stuff can be injected into the computation.
You can have the actual impl. of the method reside in an object. The method will simply delegate to that object. Thus, in order to test the method you only need to test the object (making the reasonable assumption that you got the delegation code right).

Categories

Resources