Question

I have a Backbone Model:

class DateTimeSelector extends Backbone.Model

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

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

And I have some tests for that code using jasmin and sinon.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()

It seems to work when I use it in the browser but I can't seem to get the tests to pass. Can anyone enlighten me?

Was it helpful?

Solution

duckyfuzz, you are experiencing this problem because when you are creating the spy (which actually wraps the original function and creates a level of indirection to insert its services of tracking method invocation) the binding of the events has already taken place. Which means that even though the spy wrapped the original function the event binding references the original function and not the wrapped spy. Hence, when you test, the original function gets executed on the event trigger but the spy tracking is one level above and is not executed.

To make sure that the event binding is actually pointing to the wrapped spy function you have to create the spy before create the model object (same goes if you are testing views). To do that create the spy on the prototype."method" of the class:

in the beforeEach -> section before @datetime = new DateTimeSelector() create the spy: @updateSpy = sinon.spy(DateTimeSelector.prototype, 'updateDatetime')

be sure to change your afterEach -> section where you return the prototype back to normal, like so: @updateSpy.restore()

this should be your code:

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() 

BTW, if you are using jasmin-sinon.js plugin then your syntax is fine

OTHER TIPS

You're mixing the mocking syntax of jasmine and sinon together.

In your passing test your sinon spy exposes the property calledOnce but you're using a jasmine-esque function toHaveBeenCalledOnce(). This function doesn't exist on the sinon spy so essentially no assert is taking place.

In your failing tests you're calling the jasmine spy function toHaveBeenCalled() on your sinon spy. Jasmine has its own syntax for creating a spy: spyOn(obj, 'method');

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top