I'm testing a backbone view with Jasmin, Simon and jasmin-simon.
Here is the code:
var MessageContainerView = Backbone.View.extend({
id: 'messages',
initialize: function() {
this.collection.bind('add', this.addMessage, this);
},
render: function( event ) {
this.collection.each(this.addMessage);
return this;
},
addMessage: function( message ) {
console.log('addMessage called', message);
var view = new MessageView({model: message});
$('#' + this.id).append(view.render().el);
}
});
Actually, all my tests pass but one. I would like to check that addMessage
is called whenever I add an item to this.collection
.
describe('Message Container tests', function(){
beforeEach(function(){
this.messageView = new Backbone.View;
this.messageViewStub = sinon.stub(window, 'MessageView').returns(this.messageView);
this.message1 = new Backbone.Model({message: 'message1', type:'error'});
this.message2 = new Backbone.Model({message: 'message2', type:'success'});
this.messages = new Backbone.Collection([
this.message1, this.message2
]);
this.view = new MessageContainerView({ collection: this.messages });
this.view.render();
this.eventSpy = sinon.spy(this.view, 'addMessage');
this.renderSpy = sinon.spy(this.messageView, 'render');
setFixtures('<div id="messages"></div>');
});
afterEach(function(){
this.messageViewStub.restore();
this.eventSpy.restore();
});
it('check addMessage call', function(){
var message = new Backbone.Model({message: 'newmessage', type:'success'});
this.messages.add(message);
// TODO: this fails not being called at all
expect(this.view.addMessage).toHaveBeenCalledOnce();
// TODO: this fails similarly
expect(this.view.addMessage).toHaveBeenCalledWith(message, 'Expected to have been called with `message`');
// these pass
expect(this.messageView.render).toHaveBeenCalledOnce();
expect($('#messages').children().length).toEqual(1);
});
});
As you can see addMessage
is called indeed. (It logs to the console and it calls this.messageView
as it should. What do I miss in spying for addMessage
calls?
thanks, Viktor
The function sinon. spy returns a Spy object, which can be called like a function, but also contains properties with information on any calls made to it. In the example above, the firstCall property has information about the first call, such as firstCall. args which is the list of arguments passed.
A spy in testing gives us a way of tracking calls made to a method so that we can verify that it works as expected. We use spies to check whether a method was called or not called, how many times it was called, with what arguments it was called, and also the value it returned when called.
Spies allow you to monitor a function; they expose options to track invocation counts, arguments and return values. This enables you to write tests to verify function behaviour. They can even help you mock out unneeded functions. For example, dummy spies can be used to swap out AJAX calls with preset promise values.
“A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test.” - SinonJS.
I'm not quit sure but, as I understand it, the following happens:
initialize
function and bind your view.addMessage
to your collection. view.addMessage
which means you overwrite it with a spy function. Doing this will have no effect on the function that is stored in the collection event store.So their are some problems with your test. You view has a lot of dependencies that you not mock out. You create a bunch of additional Backbone Models and Collections, which means you not test only your view but also Backbones Collection and Model functionality.
You should not test that collection.bind
will work, but that you have called bind
on the collection with the parameters 'add', this.addMessage, this
initialize: function() {
//you dont
this.collection.bind('add', this.addMessage, this);
},
So, its easy to mock the collection:
var messages = {bind:function(){}, each:function(){}}
spyOn(messages, 'bind');
spyOn(messages, 'each');
this.view = new MessageContainerView({ collection: messages });
expect(message.bind).toHaveBeenCalledWith('bind', this.view.addMessage, this.view);
this.view.render()
expect(message.each).toHaveBeenCalledWith(this.view.addMessage);
... and so on
Doing it this way you test only your code and have not dependencies to Backbone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With