【问题标题】:Why is this sinon spy not being called when I run this test?为什么我运行这个测试时没有调用这个 sinon spy?
【发布时间】:2025-12-10 16:45:01
【问题描述】:

我有一个主干模型:

class DateTimeSelector extends Backbone.Model

  initialize: ->
    @bind 'change:date', @updateDatetime
    @bind 'change:time', @updateDatetime

  updateDatetime: =>
    # do some stuff with the sate and time

我使用jasminsinon.js 对该代码进行了一些测试

describe "DateTimeSelector", ->
  beforeEach ->
    @datetime = new DateTimeSelector()

    describe "updateDatetime", ->
      beforeEach ->
        @updateSpy = sinon.spy(@datetime, 'updateDatetime')

      afterEach ->
        @datetime.updateDatetime.restore()

      # passes
      it "should be called when we call it", ->
        @datetime.updateDatetime()
        expect(@updateSpy).toHaveBeenCalledOnce()

      # fails
      it "should be called when we trigger it", ->
        @datetime.trigger 'change:date'
        expect(@updateSpy).toHaveBeenCalled()

      # fails
      it "should be called when we set the date", ->
        @datetime.set { date: new Date() }
        expect(@updateSpy).toHaveBeenCalled()

当我在浏览器中使用它时它似乎可以工作,但我似乎无法通过测试。谁能赐教?

【问题讨论】:

  • 您可能需要重新标记以包含 coffeescript。我会为你添加它,但你的最大值为 5,我不想决定为你替换哪个。
  • 是的,我不知道在这种情况下该怎么做。问题显然是用 Coffee 写的,但问题和解决方案(很可能)与咖啡脚本无关。所以我不知道标记为coffeescript是否正确。
  • 好吧,我看了这个问题,因为它被标记为 JS;但我无法提供帮助,因为该示例是我自己不使用的咖啡脚本。所以我认为咖啡脚本标签可能会吸引其他咖啡脚本用户,他们可以更轻松地阅读和理解您的示例。 :)
  • 看来你在这里测试了错误的东西。在大多数情况下,监视要测试的课程并不是一个好主意。在您的情况下,您必须测试 @updateDatetime 的结果是否是您期望的结果,而不是它是否被调用,因为这是您从骨干网获得的功能,您必须相信他们已经测试了他们的东西。

标签: javascript backbone.js jasmine coffeescript backbone-events


【解决方案1】:

duckyfuzz,您遇到了这个问题,因为当您创建 spy(它实际上包装了原始函数并创建了一个间接级别以插入其跟踪方法调用的服务)时,事件的绑定已经发生。这意味着即使间谍包装了原始函数,事件绑定也会引用原始函数而不是包装的间谍。因此,当您进行测试时,原始函数会在事件触发器上执行,但 spy tracking 是上一级的并且不会执行。

要确保事件绑定实际上指向封装的 spy 函数,您必须在创建模型对象之前创建 spy(如果您正在测试视图,也是如此)。为此,请在原型上创建间谍。类的“方法”:

@datetime = new DateTimeSelector() 之前的 beforeEach -> 部分中创建间谍:@updateSpy = sinon.spy(DateTimeSelector.原型, 'updateDatetime')

请务必更改您的 afterEach -> 部分,使原型恢复正常,如下所示:@updateSpy.restore()

这应该是你的代码:

describe "DateTimeSelector", ->
  beforeEach ->
    @updateSpy = sinon.spy(DateTimeSelector.prototype, 'updateDatetime')
    @datetime = new DateTimeSelector()

  afterEach ->
    @updateSpy.restore()

  # passes
  it "should be called when we call it", ->
    @datetime.updateDatetime()
    expect(@updateSpy).toHaveBeenCalledOnce()

  # should pass now
  it "should be called when we trigger it", ->
    @datetime.trigger 'change:date'
    expect(@updateSpy).toHaveBeenCalled()

  # should pass now
  it "should be called when we set the date", ->
    @datetime.set { date: new Date() }
    expect(@updateSpy).toHaveBeenCalled() 

顺便说一句,如果您使用的是 jasmin-sinon.js 插件,那么您的语法很好

【讨论】:

  • 这正是我的问题。谢谢。
  • 遇到了完全相同的问题,感谢提供正确的代码示例!
【解决方案2】:

您将 jasmine 和 sinon 的模拟语法混合在一起。

在您的通过测试中,您的 sinon 间谍暴露了属性 calledOnce,但您使用的是 jasmine-esque 函数 toHaveBeenCalledOnce()。 sinon spy 上不存在此功能,因此基本上没有发生断言。

在你失败的测试中,你在你的 sinon 间谍上调用了 jasmine spy 函数toHaveBeenCalled()。 Jasmine 有自己的创建间谍的语法:spyOn(obj, 'method');

【讨论】: