[Node]Backbone.js + Sinon.js で callback をテストしようとしてハマる

端的に言うと以下のコードは想定通りに動かない。(Node のコンソールで)

var Backbone = require('backbone')
var Sinon = require('sinon')

var m = new Backbone.Model({ 'foo': 'bar' })
var f = function(){ console.log("===> Callback called") }

m.on('change', f)
Sinon.spy(this, 'f')
m.set('foo', 'hoge')

f.called // => false

m.on('change', f) した時点で f の参照自体が m に束縛されていて、その後いくら spy で this.f をすげ替えても、 m のコールバックで呼ばれる処理には影響しない。

その処理以降を次のように変えてやれば動く。

m.on('change', function() { f() })
Sinon.spy(this, f)
m.set('foo', 'hoge')

m.called // => true

実際に backbone + coffee + mocha で使うときにはこんな感じになる。

# in view
class FooView extends Backbone.View
  initialize: ->
    _.bindAll @
    @model.on 'change', => @render()
    #....

# in spec (変数定義は適切な場所へ移動すべし)
  it 'change cases rendering' do
    model = new Backbone.Model foo: 'bar'
    view = new FooView model: model
    render = sinon.spy(view, 'render')
    model.set('foo', hoge')
    render.called.should.be.true


あるいは(この例の場合) render の中身の動作を spy してもよいだろう。