Синон, кажется, не шпионит за обратным вызовом обработчика событий - PullRequest
5 голосов
/ 19 января 2012

Я тестирую представление магистрали с помощью Jasmin, Simon и jasmin-simon.

Вот код:

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);
    }
});

На самом деле все мои тесты проходят, кроме одного.Я хотел бы проверить, что addMessage вызывается всякий раз, когда я добавляю элемент к 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);
    });
});

Как видите, действительно вызывается addMessage.(Он регистрируется на консоли и вызывает this.messageView, как и должно. Что мне не хватает в шпионаже для вызовов addMessage?

спасибо, Виктор

Ответы [ 2 ]

9 голосов
/ 20 января 2012

Я не совсем уверен, но, насколько я понимаю, происходит следующее:

  1. Вы создаете новое представление, которое вызывает функцию initialize, и привязываете view.addMessage к своей коллекции.,
  2. При этом Backbone берет функцию и сохраняет ее в хранилище событий вашей коллекции.
  3. Затем вы шпионите за view.addMessage, что означает, что вы перезаписываете его с помощью шпионской функции.Это не повлияет на функцию, которая хранится в хранилище событий коллекции.

Так что у вас есть некоторые проблемы с вашим тестом.У вас есть множество зависимостей, которые вы не исключаете.Вы создаете группу дополнительных опорных моделей и коллекций, что означает, что вы проверяете не только свое представление, но и опорную коллекцию и функциональность моделей.

Не следует проверять, что collection.bind будет работать, но что вы вызвали bind в коллекции с параметрами 'add', this.addMessage, this

initialize: function() {
    //you dont 
    this.collection.bind('add', this.addMessage, this);
},

Итак, коллекцию легко смоделировать:

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

Делая так, вы проверяете только свой код и независимости от Backbone.

6 голосов
/ 30 октября 2013

Как сказал Андреас в пункте 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.

Прямой ответ на вопрос, несмотря на весь удивительный рефакторинг, предложенный Андреасом, состоял бы в том, чтобы шпионить за MessageContainerView.prototype.addMessage примерно так:

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            
        ]);
        // Here
        this.addMessageSpy = sinon.spy(MessageContainerView.prototype, 'addMessage');
        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();
        MessageContainerView.prototype.addMessage.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.addMessageSpy).toHaveBeenCalledOnce();
        // TODO: this fails similarly
        expect(this.addMessageSpy).toHaveBeenCalledWith(message, 'Expected to have been called with `message`');
        // these pass
        expect(this.messageView.render).toHaveBeenCalledOnce();
        expect($('#messages').children().length).toEqual(1);
    });
});

Несмотря на это, я рекомендую реализовать предложения Андреаса. :)

...