Spying on Backbone View callback method with Jasmine - backbone.js

I have the following view:
...
var TreeView = Backbone.View.extend({
el: '#org-tree',
initialize: function() {
eventBus.on("route:change", _.bind(this.triggerFilterEvent, this));
},
render: function() { ... },
foo: function() { console.log("foo"); },
triggerFilterEvent: function(name) {
this.foo();
...
}
});
...
My Spec looks the following:
describe('TreeView', function() {
var treeView = new TreeView();
it('calls triggerFilterEvent when receiving a route:change', function() {
spyOn(treeView, 'triggerFilterEvent');
spyOn(treeView, 'foo');
treeView.delegateEvents();
eventBus.trigger("route:change", "test");
console.log('TriggerCOunt:' + treeView.triggerFilterEvent.callCount);
console.log('FooCount: ' + treeView.foo.callCount);
expect(treeView.triggerFilterEvent).toHaveBeenCalled();
});
});
I added treeView.delegateEvents() as suggested in the following solution:
SpyOn a backbone view method using jasmine
However my test still fails with:
LOG: 'triggerFilterEvent with: test'
LOG: 'Event has been triggered: route:change'
LOG: 'TriggerCOunt:0'
LOG: 'FooCount: 1'
Error: Expected spy triggerFilterEvent to have been called.
The method foo is called once as expected though, how come?

The issue is at the time you assign a spy on triggerFilterEvent, an event listener is already set to call the original function and not the spy (initialize is called when you initialize the view)
To get around that you can spy on the prototype, before you initialize the view :
spyOn(TreeView.prototype, 'triggerFilterEvent');
var treeView = new TreeView();

Related

sinon - stub method of private variable

I have been tasked with writing the tests for the following Backbone view. However I did not write the code.
In the following code example I want to stub/spy the update method so that I can check the function is called when the liked attribute of the view's model is changed, but I seem to not be able to target the method.
Is it possible to stub the update function at all?
When I run my tests it logs Hello but the test return the following error:
AssertionError: expected update to have been called at least once, but it was never called
my.Special.Class.LikeButton = function (options) {
/* ***** Other private variables ******* */
var LikeButton = Marionette.ItemView.extend({
model: null,
sSolidPoly: null,
sEmptyPoly: null,
events: {
'click': 'toggleLike'
},
initialize: function (options) {
//Listen to changes in like property in case it's changed from another location in the UI
this.listenTo(this.model, 'change:liked', this.update);
},
onRender: function () {
this.setElement(this.el.innerHTML);
},
update: function () {
console.log('Hello');
.....
}
});
return new LikeButton(options);
}
I have tried to stub the update function in the beforeEach function for the tests:
this._view = new my.Special.Class.LikeButton({
template: '#like-button-template',
model: this.model
});
this.updateStub = sinon.stub(this._view, 'update');
In my test suite:
it('change to model liked attribute calls update', function () {
var __view = this._view.render();
this.model.set({liked: true});
expect(this.updateStub).has.been.called;
});
I think this is a good example of a test suite exposing something that is too nested. Imo rather than trying to fix this you should have the nested LikeButton in a separate module and import it into that file and test it in it's own file.
Otherwise try:
var model = ...;
var likeButton = my.Special.Class.LikeButton({ model: model });
spyOn(likeButton, 'update');
model.set('liked', true);
expect(likeButton.update).toHaveBeenCalledWith(model, true);

unable to call fetch on a backbone model

In the function jsonRequest below, I can log this.model to the console, but I can't call this.model.fetch(), which I thought was the appropriate way to make a request to my server (at localhost:8080/jsonapi). The error it gives me is can't call fetch of undefined. Is there something I'm doing wrong in the code below?
var MyModel = Backbone.Model.extend({
url: 'jsonapi',
});
var MyView = Backbone.View.extend({
el: '#blahblah',
initialize: function(){
},
events: {
'click #start' : 'callJsonRequest',
},
callJsonRequest: function(){
setInterval(this.jsonRequest, 1000);
},
jsonRequest: function(){
console.log("jsonrequest", this.model); //this logs the model
this.model.fetch();
},
});
window.myModel = new MyModel();
window.startStop = new StartView({model: myModel});
You likely need to use bind to make sure this is the context of your View object.
As you mentioned in the comments, you can do:
setInterval(this.jsonRequest.bind(this), 1000);

Type Error on Backbone/Marionette Single Model Fetch

I am getting used to using Backbone and Marionette and run into a little snag that I am sure I am overlooking something. I am trying to populate my ItemView with a model from my API and I can see the request and data coming back ok but I get a Type Error:obj is undefined in what appears to be my listener:
TypeError: obj is undefined
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
Here is my Model/View
var MyDetailView = Marionette.ItemView.extend({
template: '#my-item-detail',
initialize: function () {
_.bindAll(this, 'render');
// bind the model change to re-render this view
this.listenTo(this.model, 'change', this.render);
},
tagName: "div"
})
var MyModel= Backbone.Model.extend({ urlRoot: '/api/model', intialize: function () { } });
And my code to execute:
var m = new MyModel({ id: 123});
m.fetch({
success: function (model, response) {
var view = new MyDetailView (model);
layout.content.show(view);
}
});
You'll need to pass the model in as an options hash and not just the first parameter to MyDetailView like so:
var view = new MyDetailView({ model: model });
Also for future reference Marionette does _.bindAll with render in the Marionette.View constructor.

"Uncaught TypeError: Object [object Object] has no method 'off'" error using events when moved to backbone 0.9.10

I has a view with this event:
var View = Backbone.View.extend({
el: $('#test'),
events: {
"change input": "test"
},
test: function(e) {
console.log("test");
}
});
var view = new View();
With backbone 0.9.9 it work, but with backbone 0.9.10 I got this error: Uncaught TypeError: Object [object Object] has no method 'off'. What I need to change to work with events on backbone 0.9.10?
I'm using this cdn's
http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js
http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.10/backbone-min.js
Thanks
I was using jquery 1.5.2 and the method off was introduced on jquery 1.7.0. Backbone 0.9.9 require that el contain the method unbind while Backbone 0.9.10 require that el contain the method off. The method unbind exist on jquery 1.5.2 and that was the reason why my code worked with backbone 0.9.9.
Try this...
var Test = Backbone.View.extend({
events: {
"change input": "test"
},
initialize: function() {
this.setElement($('#test'));
},
test: function(e) {
alert("test");
}
});
var test = new Test();
Or, this would be even better...
var Test = Backbone.View.extend({
events: {
"change input": "test"
},
test: function(e) {
alert("test");
}
});
var test = new Test({
el: $('#test')
});

Backbone.js event after view.render() is finished

Does anyone know which event is fired after a view is rendered in backbone.js?
I ran into this post which seems interesting
var myView = Backbone.View.extend({
initialize: function(options) {
_.bindAll(this, 'beforeRender', 'render', 'afterRender');
var _this = this;
this.render = _.wrap(this.render, function(render) {
_this.beforeRender();
render();
_this.afterRender();
return _this;
});
},
beforeRender: function() {
console.log('beforeRender');
},
render: function() {
return this;
},
afterRender: function() {
console.log('afterRender');
}
});
Or you can do the following, which is what Backbone code is supposed to look like (Observer pattern, aka pub/sub). This is the way to go:
var myView = Backbone.View.extend({
initialize: function() {
this.on('render', this.afterRender);
this.render();
},
render: function () {
this.trigger('render');
},
afterRender: function () {
}
});
Edit: this.on('render', 'afterRender'); will not work - because Backbone.Events.on accepts only functions. The .on('event', 'methodName'); magic is made possible by Backbone.View.delegateEvents and as such is only available with DOM events.
As far as I know - none is fired. Render function is empty in source code.
The default implementation of render is a no-op
I would recommend just triggering it manually when necessary.
If you happen to be using Marionette, Marionette adds show and render events on views. See this StackOverflow question for an example.
On a side note, Marionette adds a lot of other useful features that you might be interested in.
I realise this question is fairly old but I wanted a solution that allowed the same custom function to be called after every call to render, so came up with the following...
First, override the default Backbone render function:
var render = Backbone.View.prototype.render;
Backbone.View.prototype.render = function() {
this.customRender();
afterPageRender();
render();
};
The above code calls customRender on the view, then a generic custom function (afterPageRender), then the original Backbone render function.
Then in my views, I replaced all instances of render functions with customRender:
initialize: function() {
this.listenTo(this.model, 'sync', this.render);
this.model.fetch();
},
customRender: function() {
// ... do what you usually do in render()
}
Instead of adding the eventhandler manually to render on intialization you can also add the event to the 'events' section of your view. See http://backbonejs.org/#View-delegateEvents
e.g.
events: {
'render': 'afterRender'
}
afterRender: function(e){
alert("render complete")
},
constructor: function(){
Backbone.View.call(this, arguments);
var oldRender = this.render
this.render = function(){
oldRender.call(this)
// this.model.trigger('xxxxxxxxx')
}
}
like this http://jsfiddle.net/8hQyB/

Resources