Question

Im working on a test suite for an existing Backbone application using Jasmine and Sinon and I am testing that my router performs the correct actions on a certain route. Here's the actual route function:

favourites: function()
{
    //Dont re-initialize the favourites view as there is no need.
    //Instead, just render the favourite movies
    if ( ! this.favMoviesView)
    {
        this.favMoviesView = new cinephile.Views.FavouriteMoviesView({
            collection: cinephile.favouriteMovies
        });
    }
    else
    {
        this.favMoviesView.renderFavourites();
    }

    $('#content').html(this.favMoviesView.el);
},

In my test suite I want to assert that when navigating to to the favourites route this.favMoviesView will be created once and then, if it exists will not re-initialize but instead just call this.favMoviesView.renderFavourites() which is a method that iterates over the view's collection.

Here's my test spec:

describe('cinephile.Routers.CinephileRouter', function () {

    beforeEach(function () {

        this.router = new cinephile.Routers.CinephileRouter();
        this.routeSpy = sinon.spy();

        try
        {
            Backbone.history.start({ silent : true });
        }
        catch(e) {}

        this.router.navigate('elsewhere');

        this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView')
            .returns(new Backbone.View());
    });

    afterEach(function () {
        this.favouritesViewStub.restore();
    });

    describe('Favourites Route', function() {

        it('should load the favourites on /favourites', function () {

            this.router.bind('route:favourites', this.routeSpy);
            this.router.navigate('favourites', true);

            expect(this.routeSpy.calledOnce).toBeTruthy();
            expect(this.routeSpy.calledWith()).toBeTruthy();
        });

        it('creates a favourites view if one doesn\'t exist', function () {
            this.router.favourites();
            expect(this.favouritesViewStub.calledOnce).toBeTruthy();
        });

        it('Reuses the favourites view if one does exist and reset it\'s collection', function () {
            this.router.favourites();
            this.router.favourites();

            expect(this.favouritesViewStub.calledOnce).toBeTruthy();
            expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();
        }); 
    });
});

My first two tests pass and I believe them to correctly describe the favourites method in my router. The third test is the the one giving me problems. As I understand it, because I am testing my router and NOT the FavouriteMoviesView I should be stubbing out the view to keep the test isolated. If that is the correct assumption, my issue becomes that the stub won't have a renderFavourites method as it is a stubbed out Backbone.View().

How can I fix this particular problem and if you are so inclined, I believe I'm missing something conceptual so feel free to explain what it is that I'm not understanding.

Cheers.

Was it helpful?

Solution

You problem is that you want to mock something inside a mock function. What I would suggest is that instead of this...

this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new Backbone.View());

...have this:

var StubView = Backbone.View.extend({
  renderFavourites: sinon.stub()
});
this.favouritesViewStub = sinon.stub(cinephile.Views, 'FavouriteMoviesView').returns(new StubView());

Now your View "constructor" will return a StubView, which has the method you are calling stubbed out. So this Backbone View with the stubbed out method will be placed in the router.favMoviesView -property. The favouritesViewStub property still contains just the "constructor" -function, so you can't access this stubbed method from there. This is why you haveto change this from the last test:

expect(this.favouritesViewStub.renderFavourites).toHaveBeenCalledTwice();

to something like this:

expect(this.router.favMoviesView.renderFavourites).toHaveBeenCalledTwice();

This way you will actually check if the router's copy of the view has had the method called twice.

Hope this works for you, comment if it doesn't! I didn't test this out, so there could be some problems, but I'm sure the logic behind works.

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