Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sinon does not seem to spy for an event handler callback

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

like image 512
Akasha Avatar asked Jan 19 '12 18:01

Akasha


People also ask

How does Sinon spy work?

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.

What are spies testing?

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.

What is spy in JavaScript?

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.

What is spy in mocha?

“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.


1 Answers

I'm not quit sure but, as I understand it, the following happens:

  1. You create a new view which calls the initialize function and bind your view.addMessage to your collection.
  2. Doing this, Backbone take the function and store it in the event store of your collection.
  3. Then you spy on 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.

like image 123
Andreas Köberle Avatar answered Sep 22 '22 15:09

Andreas Köberle