Spying on Backbone.js route calls with Jasmine

Having problems spying method calls on a Backbone Router to ensure it calles the right method on a given route.

excerpt from the test

  • Jasmine.js v2.0.0 - TypeError: Cannot read property 'stopPropagation' of undefined
  • Backbone listenTo propertchange
  • How to get Grunt-Contrib-Jasmine to execute specs and load dependencies?
  • How to protect against CSRF when using Backbone.js to post data?
  • How to toggle backbone.js's model attributes?
  • Communicating between views design patterns with events
  • describe 'Router', ->
        beforeEach ->
            @router = new App.Router()
            Backbone.history.start()
    
        afterEach ->
            Backbone.history.stop()
    
        describe 'routes', ->
             it 'should be defined', ->
                  expect(@router.routes).toBeDefined()
    
             describe 'default route', ->
                 it 'should be defined', ->
                      expect(@router.routes['']).toBeDefined()
    
                 it 'should call index', ->
                     spy = spyOn(@router, "index")
                     @router.navigate('', true)
                     expect(spy).toHaveBeenCalled()
    

    The router

    class App.Router extends Backbone.Router
        routes:
            '' : 'index'
    
        index: ->
            console.log "router.index has been called"
    

    Everything passes except the last test “should call index”.
    It fails with the message “Expected spy index to have been called”.
    Ive tried other variants

    it "should call index", ->
        spyOn(@router, "index")
        @router.navigate('', true)
        expect(@router.index).toHaveBeenCalled()
    

    I can also see the “router.index has been called” log output in the test output from the original Router.index function

    Thanks!

    EDIT:
    One solution

    describe '#1 Solution', ->
        it 'should call index', ->
            spyOn(App.Router.prototype, "index")
            @router = new App.Router()
            Backbone.history.start()
            @router.navigate('', true)
            expect(App.Router.prototype.index).toHaveBeenCalled()
    

  • CORS GET returns an empty response body in Firefox
  • How can I have a dynamically allocated DOM event fire as well as a backbone event?
  • Extending a custom Backbone.Marionette view. How to implicitly incur prototype's events/onRender?
  • When to use Marionette vs. pure Backbone
  • Backbone.js Routing: From hash to hashbang
  • Underscore template display array of objects via _.each
  • 2 Solutions collect form web for “Spying on Backbone.js route calls with Jasmine”

    It has took too much time to me to come with a working jsFiddle and the question has been already answered by @MarkRushakoff.

    Still I have some comments.

    The way Backbone is binding the routes make very difficult to test it.

    The point is that the router methods are not called directly in the router instance, the methods are taked as callbacks and stored in an internal Backbone.history.route waiting for execution, check the Backbone.Router.route code.

    This operation is done in the moment the Router is instantiate, so you have to spy your Router.method before you instantiate the reference, so for you have to delay Backbone.history.start also after the spy has been activated.

    As you have to declare the spy before the router instance is created you have to do it in a Class level.

    Said so this is the simplest solution I came with:

    describe("Router", function() {
      afterEach( function(){
        Backbone.history.stop();
      });
    
      it("should call index", function(){
        spyOn(App.Router.prototype, "index")
        var router = new App.Router(); // instance created after spy activation
        Backbone.history.start();      // it has to start after the Router instance is created
    
        router.navigate('', true);
    
        expect(App.Router.prototype.index).toHaveBeenCalled();  
      });
    });
    

    Conclusion, I think the Backbone.Router implementation has not an intuitive design.

    I’m pretty sure this has to do with the way that Backbone binds to its routing methods when you use a routes hash (especially if you’re seeing a console log correctly output). That is, the router has bound to the original index method, but your spy has replaced the “current” index method.

    You have two options:

    • spyOn(@router, "index") before you the router binds to the routes (may be difficult)
    • Spy on the prototype’s index method: spyOn(App.router.prototype, "index"); @router.navigate('', true); expect(App.router.prototype.index).toHaveBeenCalled();