Test Marionette vent.on Listener - backbone.js

I have a Layout that has a vent.on call in it that gets setup on model initiation.
initialize: () ->
App.vent.on("deciseconds:updated", this.updateDVR)
updateDVR: () ->
console.log 'this updateDVR is called'
I want to make sure this.updateDVR is hooked up correctly in my app. In my tests I have this:
beforeEach ->
this.showDvr = new Arc.DvrApp.Show.DvrView()
spyOn(this.showDvr, 'updateDVR')
it "calls updateDvr when new data comes in", ->
Arc.vent.trigger("deciseconds:updated")
expect(this.showDvr.updateDVR).toHaveBeenCalled()
This spec is failing, but when I check my log I am seeing this updateDVR is called, the line I was logging in the updateDVR function. So I know the function is being called.
It I directly call updateDVR, the spec passes:
it "calls updateDvr when new data comes in", ->
this.showDVR.updateDVR()
expect(this.showDvr.updateDVR).toHaveBeenCalled()
I thought the vent might be treated as an asynchronous function, so I tried waiting a few seconds before the expect clause to see if that would work, but it didn't:
it "calls updateDvr when new data comes in", ->
Arc.vent.trigger("deciseconds:updated")
setTimeout(myCheck, 3000)
myCheck = () ->
expect(this.showDvr.updateDVR).toHaveBeenCalled()

The call to App.vent.on in your initialize function passes a reference to the view instance's this.updateDVR function -- and that happens right before you spyOn(this.showDvr, ...) in the test's beforeEach. So when you trigger the event, the trigger calls that retained reference to the actual updateDVR function, not the spy.
You should be able to fix it by passing a callback function to the App.vent.on call, like so (sorry for the javascript, I'm not a coffeescripter!):
initialize: function () {
var that = this;
// hold onto the callback so we can unregister it in App.vent.off, too
this.onUpdateCb = function() { that.updateDVR() };
App.vent.on("deciseconds:updated", this.onUpdateCb );
// if this is a view, stop listening for App.vent when the view closes
this.listenTo(this, 'close', function() { App.vent.off("deciseconds:updated", this.onUpdateCb ) } );
}
That will make the event handler look up the symbol named "updateDVR" at the time the event is triggered, & it will call your spy as your test expects.
Edit: Updated to retain this.onUpdateCb so we can unregister the listener on close.

Related

Angular 8 Jasmine/Karma spyOn behavior questions

I have a directive with custom debounce decorators. The directive is a fairly simple that listen on "scroll" event.
export class MyScrollDirective {
#Output() scrolled = new EventEmitter<any>();
#HostListener('scroll', ['$event'])
//debouce is added to prevent multiple call when scroll reach the end
//in the if statement below
#myDebounce(300)
onScroll(event) {
this.scrolled.emit(null);
}
}
In my test, I have an assumption that this.scrolled.emit() will be called whenever onScroll is called. Obviously this is not the case.
With the debounce, it looks like onScroll can be called multiple times from spyOn while this.scrolled only emit once. For example, if I trigger scroll events 2 times in my test within 300ms intervals, the onScroll is called 2 time while the this.scrolled has only 1 emit. The 1 emit is of course desired behavior, but how come onScroll is called twice. When the app is actually running in browser - instead of tests - the onScroll is actually called only once.
Why is this?!
The test detail can be view at StackBliz.io
This is a very interesting question!
In order to get a better understanding of how things work, I'd recommend opening this forked StackBlitz and place these breakpoints:
jasmine.js
calls.push(context); - line 2162
{ - line 5972
myDebounceDecorator.ts
descriptor.value - line 16
var params = []; - line 18
dummy.component.spec.ts
vsCss.triggerEventHandler(...) - line 36
vsCss.triggerEventHandler(...) - line 40
Note: I've used Firefox Dev Tools
This is what happens after you refresh the app:
descriptor.value; is reached; this means that the spy will be applied on the newly created function:
descriptor.value = function () { /* this will be spied */ }
vsCss.triggerEventHandle (line 36) is reached
var callData = { is reached, because of the spy.
calls.push(context); is reached, which means the spy(onScroll) was called
var params = []; is reached, because the spy was defined with callThrough(), which means that the original implementation will be used
vsCss.triggerEventHandler (line 40) is reached
var callData = { is reached, because of the spy
calls.push(context); is reached again; if we were to hover over calls, we'd see that it already has an element(from the previous vsCss.triggerEventHandle), hence the calls' length will be 2
var params = []; - the original implementation is used
var callData = { is reached; this time, it's the spy what we used on the Subject(i.e EventEmitter); we can be sure of this by hovering over this from object: this
calls.push(context); - calls belongs to the Subject's spied method
var callData = { is reached; this time, the spy belongs to onDummy method
calls.push(context); - onDummy's spy

Backbone, Marionette, Jasmine: How to test jQuery deferred event

I'm very new to Jasmine and Marionette and looking for some help on how to test and even just the proper way to think about testing my application. Any pointers are welcome.
I have a Marionette Controller that I use to fetch my model, instantiate my views and render them. I use a method found at the bottom of this page so that the model is fetched before the view is rendered: https://github.com/marionettejs/backbone.marionette/blob/master/upgradeGuide.md#marionetteasync-is-no-longer-supported.
My controller method to fetch the model and display the view looks like so:
showCaseById: function(id){
App.models.Case = new caseModel({ id: id });
var promise = App.models.Case.fetch();
$.when(promise).then(_.bind(this.showContentView, this));
},
As you can see, it calls the showContentView after the model is fetched. That method is here:
showContentView: function(model){
App.views.Body = new bodyView({
model: App.models.Case
});
App.views.Body.on('case:update', this.submitCase, this);
// this.layout is defined in the controller's initialize function
this.layout.content.show(App.views.Body);
},
What is the proper way to test this functionality? I'd like to test the calling of the showContentView function after the completion of the promise. How should I break up the specs for this?
Thanks.
First, spy on your showContentView method and assert it has been called:
it('showCaseById', function (done) {
var controller = new Controller();
spyOn(controller, 'showContentView');
controller.showCaseById('foo');
expect(controller.showContentView).toHaveBeenCalledWith(jasmine.any(caseModel));
});
Secondly, I would recommend you stub out the call to fetch() so you don't hit the network, but it's starting to get a bit hairy now:
function caseModel() {
this.fetch = function () {
// return a promise here that resolves to a known value, e.g. 'blah'
};
}
Now, you can have a slightly stronger assertion, but this is a bit shonky because you're fiddling around with internals of your dependencies:
expect(controller.showContentView).toHaveBeenCalledWith('blah');
By overriding caseModel, when your controller method goes to create one, it gets your new version instead of the old one, and you can control the implementation of the new one just for this test.
There are ways to make this code more testable, but as it seems you're just starting out with testing I won't go into it all. You'll certainly find out those things for yourself as you do more testing.
First, it's important to understand that _.bind(fn, context) doesn't actually call fn. Instead, it returns a function that when called will call fn(). The context defines the object that fn will use internally as this.
It's not necessary but you could write showCaseById as :
showCaseById: function(id){
App.models.Case = new caseModel({ id: id });
var promise = App.models.Case.fetch();
var fn = _.bind(this.showContentView, this);
$.when(promise).then(fn);
},
As I say, that is unnecessary but now you understand that _.bind() returns a function and that $.when(promise).then(...) accepts a function as its (first) argument.
To answer the actual question, you can confirm that the App.models.Case.fetch() promise has been fulfilled by adding a further $.when(promise).then(...) statement, with a test function of your own choosing.
showCaseById: function(id){
App.models.Case = new caseModel({ id: id });
var promise = App.models.Case.fetch();
$.when(promise).then(_.bind(this.showContentView, this));
// start: test
$.when(promise).then(function() {
console.log("caseModel " + id + " is ready");//or alert() if preferred
});
// fin: test
},
The second $.when(promise).then(...) will not interfere with the first; rather, the two will execute sequentially. The console.log() satatement will provide reliable confirmation that the this.showContentView has been called successfully and that initial rendering should have happened.
If nothing is rendered at this point or subsequently, then you must suspect that this.showContentView needs to be debugged.

Collection fetch doesnt fire reset

I am just going crazy as why is this not working. I have a collection that fetches the data. The collection is bind to run render function on reset. But render isnt called.
this.uploadTemplate = _.template(UploadTemplate);
this.artistCollection = new ArtistCollections();
this.artistCollection.bind('reset', this.render, this);
this.artistCollection.fetch({success: function(){console.log('jsjbajsb');},
error: function(collection, response, options){ console.log(response)}});
// this.artistCollection.fetch({reset: true});
this.me = options.me;
Neither is the success function called nor the commented line works
Edit :1 Added error option
So, I was not sending a correct Json-fied response. Lots of thnks to #mu is too short.
I wish backbone would have provided a verbose error in this case.

Qunit + Sinon to test Backbone's model events

These days I'm trying to put up some tests for my first serious Backbone app. I had no problem so far with normal test but now I'm stuck trying to setting up an async test.
Basically my server API return a page with a 500 HTTP code error if I try to save a model with invalid attributes and I want to check if this trigger the right "error" state in Backbone.
I've tried to set-up the test in this way:
asyncTest("Test save Model function", function(){
expect(1);
var user = new User({});
var err_spy = this.spy();
user.on('error',err_spy);
user.save(user,{error:function(){
start();
equal( err_spy.callCount, 1, "Callback 'error' called once");
}});
});
The problem is that the error callback of the save function overrides the one in the model, so the only way to trigger it would be to do it manually:
user.trigger("error");
I don't think it is a right way to test because in my production environment there is no error callback for model's save function, but on the other hand I don't know how to tell Qunit to wait the ajax response to evaluate the test assertion.
Can someone suggest me a way to make it work? Thank you!
Something like this should do the trick. I'm going from memory here, but the sinon fake server should allow you to immediately return the 500 error state and subsequently invoke the spied-on function. You might need to tweak the server.respondWith(...) call.
asyncTest("Test save Model function", function(){
expect(1);
var user = new User({});
// Set up a fake 500 response.
var server = sinon.fakeServer.create();
server.respondWith(500, {}, "");
// Create the error callback.
var err_callback = function(){};
var err_spy = sinon.spy(err_callback);
user.save(user, {error: err_callback});
server.respond();
equal( err_spy.callCount, 1, "Callback 'error' called once");
server.restore();
});

Backbone.Model.save does not set my model with the server response

I'm calling 'save' on my model and returning the new model as json in my PHP backend. When I step through the Backbone.Model.save method, I can see that it successfully gets the server response and then sets the model (in the options.success closure below). But when the execution is returned to my click handler, the model has the old properties (ie. the id is not set). What could be happening?
Here is my click handler:
addButtonClick: function(e) {
var data = $(e.target).closest('form').serializeObject();
var p = new Domain.Page; // this extends Backbone.Model
p.set(data);
p.save();
// ****
// **** AFTER p.save, the model p still has the OLD ATTRIBUTES... why??
// ****
return false;
}
Here is Backbone's save method:
// Set a hash of model attributes, and sync the model to the server.
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save : function(attrs, options) {
options || (options = {});
if (attrs && !this.set(attrs, options)) return false;
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
// ****
// **** NEXT LINE SUCCESSFULLY SETS THE MODEL WITH THE SERVER RESPONSE
// ****
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp, xhr);
};
options.error = wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
return (this.sync || Backbone.sync).call(this, method, this, options);
},
The save method is asynchronous. In other words, the model.set call inside save happens after the server has responded.
You are asking why the values are the same immediately after save is called. The answer is: at that point in time, the response has not been received by your code yet. The callback hasn't been called. model.set hasn't been called.
When you continue on and the event loop gets the response from the server (this may be a fraction of a second, it may be several seconds) later, your values will get set.
I think I figured out what was wrong here. And Brian you were right to say it had something to do with the async nature of the Backbone.save call. The thing is that I was using the DEBUGGER. This stops all execution. I actually don't really understand how an async call works under the hood, perhaps with threads? I assumed that after I stepped over the call to 'save' and then waited a sec, then the async part (whatever that is) of the 'save' call would execute in the background. But this is not the case. The debugger halts everything. So the options.success closure within 'save' always gets called sometime after stepping over 'save'. In short, this whole thing is due to me not understanding javascript and javascript debugging properly.

Resources