31 votes

Tester correctement les routeurs dans backbone.js?

Donc j'ai simplement commencé à écrire des tests pour mon en-cours javascript application, à l'aide de sinon.js & jasmine.js. Fonctionne assez bien dans l'ensemble, mais j'ai besoin aussi d'être en mesure de tester mes routeurs.

Les routeurs, dans leur état actuel, va déclencher un certain nombre de points de vue et d'autres choses, la résiliation de l'actuel jasmine.js test en invoquant Backbone.navigate dépend de l'état de l'application et de l'INTERFACE utilisateur itneraction.

Alors, comment ai-je pu tester que le routage à différents endroits permettrait de travail, tout en gardant les routeurs "bac à sable" et de ne pas leur permettre de changer d'itinéraire?

Puis-je installer une sorte de simulacre de fonction qui permettra de surveiller pushState changements ou similaires?

37voto

ggozad Points 8910

Voici un bas-levelish moyen de le faire avec du jasmin, de l'essai que pushState fonctionne comme prévu et que votre routeur met en place les choses correctement... Je suppose un router qui a été initialisé et a une maison de l'itinéraire tracé à l' ". Vous pouvez adapter ce pour votre d'autres voies. J'ai aussi supposer que vous avez fait dans votre application phase d'initialisation, une Backbone.history.start({ pushState: true });

    describe('app.Router', function () {

        var router = app.router, pushStateSpy;

        it('has a "home" route', function () {
            expect(router.routes['']).toEqual('home');
        });

        it('triggers the "home" route', function () {
            var home = spyOn(router, 'home').andCallThrough();
            pushStateSpy = spyOn(window.history, 'pushState').andCallFake(function (data, title, url) {
                expect(url).toEqual('/');
                router.home();
            });
            router.navigate('');
            expect(pushStateSpy).toHaveBeenCalled();
            expect(home).toHaveBeenCalled();
            ...
        });
    });  

Vous pouvez effectivement obtenir les mêmes choses en faisant Backbone.history.stop(); il est fait pour cette raison.

Mise à JOUR: les Navigateurs sans pushState:

Bien sûr, cela fonctionnera bien si votre navigateur vous le tester sur supporte pushState. Si vous testez contre les navigateurs qui ne le font pas, vous pouvez conditionnellement test comme suit:

it('triggers the "home" route', function () {
    var home = spyOn(router, 'home').andCallThrough();

    if (Backbone.history._hasPushState) {
        pushStateSpy = spyOn(window.history, 'pushState').andCallFake(function (data, title, url) {
            expect(url).toEqual('/');
            router.home();
        });
        router.navigate('', {trigger: true});
        expect(pushStateSpy).toHaveBeenCalled();
        expect(home).toHaveBeenCalled();

    } else if (Backbone.history._wantsHashChange) {
        var updateHashSpy = spyOn(Backbone.history, '_updateHash').andCallFake(function (loc, frag) {
            expect(frag).toEqual('');
            router.home();
        });
        router.navigate('', {trigger: true});
        expect(updateHashSpy).toHaveBeenCalled();
        expect(home).toHaveBeenCalled();
    }
});

Si vous êtes sur IE6, bonne chance.

8voto

Chris Jaynes Points 1527

Lorsque je teste un routeur dorsal, ce qui m'importe, c'est que les routes que j'ai fournies invoquent les fonctions que je spécifie avec les arguments corrects. Beaucoup d'autres réponses ici ne testent pas vraiment cela.

Si vous devez tester la fonctionnalité de certaines routes, vous pouvez tester ces fonctions par elles-mêmes.

En supposant que vous ayez un routeur simple:

 App.Router = Backbone.Router.extend({
  routes: {
    '(/)':'index',
    '/item/:id':'item'
  },
  index: {
    //render some template
  }, 
  item: {
    //render some other template, or redirect, or _whatever_
  }
});
 

Voici comment je le fais:

 describe('Router', function() {

  var trigger = {trigger: true};
  var router

  beforeEach(function() {
    // This is the trick, right here:
    // The Backbone history code dodges our spies
    // unless we set them up exactly like this:
    Backbone.history.stop(); //stop the router
    spyOn(Router.prototype, 'index'); //spy on our routes, and they won't get called
    spyOn(Router.prototype, 'route2'); 

    router = new App.Router(); // Set up the spies _before_ creating the router
    Backbone.history.start();
  });

  it('empty route routes to index', function(){
    Backbone.history.navigate('', trigger);
    expect(router.index).toHaveBeenCalled();
  });

  it('/ routes to index', function(){
    router.navigate('/', trigger);
    expect(router.index).toHaveBeenCalled();
  });

  it('/item routes to item with id', function(){
    router.navigate('/item/someId', trigger);
    expect(router.item).toHaveBeenCalledWith('someId');
  });
});
 

4voto

Industrial Points 8386

Voici ce que j'ai fini par utiliser moi-même. J'ai fait une version fictive du routeur en l'étendant et en remplaçant les méthodes par une méthode vide pour l'empêcher d'invoquer une logique supplémentaire lors de son appel:

 describe("routers/main", function() {

    beforeEach(function() {

        // Create a mock version of our router by extending it and only overriding
        // the methods
        var mockRouter = App.Routers["Main"].extend({
            index: function() {},
            login: function() {},
            logoff: function() {}
        });

        // Set up a spy and invoke the router
        this.routeSpy = sinon.spy();
        this.router = new mockRouter;

        // Prevent history.start from throwing error
        try {
            Backbone.history.start({silent:true, pushState:true});
        } catch(e) {

        }

        // Reset URL
        this.router.navigate("tests/SpecRunner.html");
    });

    afterEach(function(){
        // Reset URL
        this.router.navigate("tests/SpecRunner.html");
    });

    it('Has the right amount of routes', function() {
        expect(_.size(this.router.routes)).toEqual(4);
    });

    it('/ -route exists and points to the right method', function () {
        expect(this.router.routes['']).toEqual('index');
    });

    it("Can navigate to /", function() {
        this.router.bind("route:index", this.routeSpy);
        this.router.navigate("", true);
        expect(this.routeSpy.calledOnce).toBeTruthy();
        expect(this.routeSpy.calledWith()).toBeTruthy();
    });

});
 

Notez que sinon.js est utilisé ci-dessus pour créer l'espion, ainsi que underscore.js pour fournir la fonction size .

2voto

Michal Points 342

Il existe un très bon tutoriel sur le test de la dorsale:

http://tinnedfruit.com/2011/04/26/testing-backbone-apps-with-jasmine-sinon-3.html

1voto

Andreas Köberle Points 16453

Vous devez vous moquer de Backbone.Router.route qui est la fonction utilisée en interne pour lier les fonctions à Backbone.History.

C'est la fonction d'origine:

 route : function(route, name, callback) {
  Backbone.history || (Backbone.history = new Backbone.History);
  if (!_.isRegExp(route)) route = this._routeToRegExp(route);
  Backbone.history.route(route, _.bind(function(fragment) {
    var args = this._extractParameters(route, fragment);
    callback.apply(this, args);
    this.trigger.apply(this, ['route:' + name].concat(args));
  }, this));
}
 

vous pouvez faire quelque chose comme ça, qui appelle simplement les fonctions lorsque le routeur sera initialisé:

 Backbone.Router.route = function(route, name, callback) {
    callback();
}
 

Vous pouvez également enregistrer les rappels dans un objet et avec l'itinéraire comme nom et appeler les mêmes étapes par étape:

 var map = {}
Backbone.Router.route = function(route, name, callback) {
    map[route] = callback();
}

for(i in map){
    map[i]();
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X