Sinon似乎没有监视事件处理程序回调

Sinon does not seem to spy for an event handler callback

本文关键字:事件处理 程序 回调 监视 Sinon      更新时间:2023-09-26

我正在与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);
    }
});

事实上,我所有的测试都通过了,只有一次通过。每当我向this.collection添加项目时,我想检查是否调用了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            
        ]); 
        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调用时,我错过了什么?

谢谢,Viktor

我不确定,但据我所知,会发生以下情况:

  1. 您将创建一个新视图,该视图调用initialize函数并将view.addMessage绑定到集合
  2. 执行此操作时,Backbone获取函数并将其存储在集合的事件存储中
  3. 然后你监视view.addMessage,这意味着你用间谍功能覆盖它。这样做不会影响存储在集合事件存储中的函数

所以你的测试有一些问题。您的视图有很多依赖项,但您没有模拟出来。您将创建一组附加的主干模型和集合,这意味着您不仅要测试视图,还要测试主干集合和模型功能。

您不应该测试collection.bind是否可以工作,而应该测试您已经使用参数'add', this.addMessage, this在集合上调用了bind

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。

正如Andreas在第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.

这个问题的直接答案,不考虑Andreas建议的所有令人敬畏的重构,就是像这样监视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);
    });
});

无论如何,我确实建议执行Andreas的建议。:)