Comment tester devise ? réélement ?
Alors que j'ai cherché comment tester facilement Devise. J'ai indiqué une technique dans mon précédent post. Mais cette technique est loin d'être la meilleure. Voici donc la nouvelle solution, la solution officielle.
Il suffit d'inclure Devise::TestHelpers. Ensuite pour se logger avec un utilisateur, on utilise la méthode sign_in. La méthode sign_out existe aussi.
Devise ? c'est bien, mais il faut le tester.
Alors que j'ai évoqué ma migration de Merb à Rails pour Oupsnow, il a fallu trouver un système d'authentification ORM Agnostique.
Le plugin d'authentification le plus connu à l'heure actuel est Authlogic. Ce plugin est vraiment très performant, mais tous les essais de le rendre ORM Agnostique ont été vain. C'est alors qu'au même moment, durant le Rails Summit 2009, George Guimarães et Carlos Antonio annoncent la sortie de Devise, un plugin Rails au dessus de Warden ( Rack middleware d'authentification). C'est exactement, ce qu'il me faut, un nouveau système d'authentification a tester et peut-être une possibilité d'ajouter une couche d'ORM Agnostique dedans. En plus Warden étant un RackMiddleware, je pourrais un peu tester ce que ça donne.
J'installe donc Devise et commence à l'utiliser dans Oupsnow. Tout se passe à merveille, jusqu'au moment où il faut faire les tests. Tout de suite le bât blesse. Les tests controlleurs de Rails ne communiquent pas avec la couche Rack qui n'est pas initialisée. On se retrouve donc avec une impossibilité de définir si un utilisateur est loggé ou non et si oui, qui est cet utilisateur.
Après de nombreux tests et essais. J'ai fini par trouver comment faire.
Warden ajoute à la requête une variable d'environment dans la requête. On peux
y accéder par
request.env['warden']. Il suffit donc de remplir cette
variable.
Pour avoir un utilisateur non loggé, il faut faire :
def unlogged request.env['warden'] = Warden::Proxy.new(request.env, {:default_strategies => [:rememberable, :authenticable],:silence_missing_strategies => true}) end
Pour se logger avec un utilisateur en particulier il faut faire :
def logged_as(user) proxy = Warden::Proxy.new(request.env, {:default_strategies => [:rememberable, :authenticable], :silence_missing_strategies => true}) proxy.set_user(user, :store => true, :scope => :user) request.env['warden'] = proxy end
Personnellement, j'aime beaucoup devise. A tel point que j'ai permis de le rendre ORM Agnostique et compatible avec MongoMapper.
EDIT du 30 Novembre 2009: la technique indiquée ici n'est pas optimum et ne marche pas avec les dernières versions de Devise. utilisez plutôt la technique décrite dans mon ticket Comment tester devise ? réélement ?
[...]Pourquoi Rspec au lieu de Test::Unit?
Dernièrement, on m'a posé la question toute bête :
Pourquoi Rspec au lieu de Test::Unit ?
C'est vrai que je suis un adepte de Rspec et que je n'utilise que ça si c'est possible. mais je n'ai jamais expliqué ici clairement ce qui me pousse dans ce choix. Donc voici un résumé très bref de mon choix.
Les formateurs
Une des options méconnues de Rspec est la possibilité de définir son formateur. Il existe ainsi plusieurs formateurs base. On peux ainsi avoir un retour direct sous format HTML ou text avec les points ( classique avec Test::Unit). Mais certain formateur peuvent aussi calculer le temps que dure un exemple. Ainsi nous avons une facilité de détection des test les plus lent pour les refactorer et essayer d'améliorer le temps de réalisation de ses tests. Dans ce sens, j'ai moi même créer mon propre formateur Rspec.
Les tests partagés
Rspec dispose d'une possibilité sous estimés, les tests partagés. En effet, on peux définir certain jeux de test comme "partageable" on a ainsi la possibilité de les incorporer facilement dans divers tests. C'est une fonction vraiment pratique dans le cadre de test fonctionnel principalement. Plus besoin de copier/coller son code. :)
Compatibilité Test::Unit
Le passage a Rspec est assez aisé de part sa compatibilité avec Test::Unit. En effet, si vous n'êtes pas familier ou que vous n'aimez pas la formalation my_array.should be_empty, rien ne vous y oblige. Vous pouvez tout à fait utiliser la syntaxe Test::Unit assert my_array.empty?. La migration s'en trouve très largement aisée.
Et si on écrivait une histoire à Rails ?
Sous ce titre particulier, je voudrais parler un peu des stories de Rspec. En effet, depuis la version 1.0 de RSpec, les stories ont été ajouté à Rspec. Ce système de story peux tout à fait être utilisé au sein de Rails pour permettre de créer des test d'intégrations. En effet, avec les spec traditionnels, nous ne pouvons pas réellement réaliser de test d'intégration. Avec les story nous pouvons le faire très facilement, que ca soit lisible et surtout très facilement répétable et modifiable. Mais le mieux est de vous montrer comment l'intégrer et l'utiliser à Rails.
Si vous avez installer Rspec pour Rails et lancé la génération de rspec alors vous devez avoir dans votre arborescence un dossier stories. C'est en général dans ce dossier que nous mettons les stories.
Votre première histoire
Nous allons supposer que vous avez un petit système de login et que vous voulez le tester. Il suffit de créer le fichier login_story.rb comme ci-dessous
login_story.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Story "Un Utilisateur s'authentifie", %{ Je suis un utilisateur et je veux m'authentifier Alors j'y arrive }, :type => RailsStory do Scenario "Logged successful" do Given "un login", "shingara" do |login| @login = login end And "un mot de passe", "mon_password" do |password| @password = password end And "l'utilisateur est creer" do User.create {:login => @login, :password => @password} end When "je vais sur", "/" do |page| get page end Then "je suis redirige vers la page de login" do response.should redirect_to '/login" follow_redirect! end When "Je me log" do post "/login", :login => @login, :password => @password end Then "Je suis redirige vers ", "/" do |path| response.should redirect_to ("/") follow_redirect! end end
Voici donc un test de story avec Rails. Il faut bien penser à utiliser le type RailsStory pour que Story soit un Objet qui hérite de ActionController::IntegrationTest et puisse ainsi avoir toutes les méthodes utilent comme le follow_redirect!. Par contre, il faut faire attention, car le load des fixtures n'existent pas dans les Story, il faut créer soit-même un étape qui le fasse pour que cela soit possible.
Et en plus répétable ?
Maintenant une autre grande fonctionnalité des Story de Rspec est la possibilité de faire des stories en plain-text. Ainsi notre story précédente deviendrait tout simplement :
login_story.rbStory: Un Utilisateur s'authentifie Je suis un utilisateur et je veux m'authentifier Alors j'y arrive Scenario: Logged successful Given un login shingara And un mot de passe mon_password And l'utilisateur est creer When je vais sur / Then je suis redirige vers la page de login When Je me log Then Je suis redirige vers /
A ce moment là plus besoin d'être développeur pour comprendre ce que fait le test. Pour cela en fait il faut créer toute une batterie de jeu d'étapes. Les étapes sont de 3 Types :
- Given : qui sont les données de base ou postulat
- When : qui sont les actions
- Then : qui sont les résultats
Il suffit donc de créer un fichier d'étapes qui comprenne toutes les étapes indiqués dans le fichier texte :
login_steps.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 steps_for(:gallery) do Given "un login $login" do |login| @login = login end And "un mot de passe $password" do |password| @password = password end And "l'utilisateur est creer" do User.create {:login => @login, :password => @password} end When "je vais sur $page do |page| get page end Then "je suis redirige vers la page de login" do response.should redirect_to '/login" follow_redirect! end When "Je me log" do post "/login", :login => @login, :password => @password end Then "Je suis redirige vers $path" do |path| response.should redirect_to ("/") follow_redirect! end end
En indiquant que le fichier texte prend appui sur ce fichier d'étape, Rspec fait tout le reste. On appelle notre test de la façon suivante :
all.rb
1 2 3 4 5 require File.join(File.dirname(__FILE__), *%w[helper]) with_steps_for(:login) do run File.join(File.dirname(__FILE__), "login_story", :type => RailsStory end
Ce qui est vraiment génial dans tout ça c'est la possibilité du coup de changer l'ordre des étapes voir de leur paramètres. Ainsi le test suivant peux être réalisé sans nouveau code ruby d'ajouté :
login_story.rbStory: Un Utilisateur s'authentifie Je suis un utilisateur et je veux m'authentifier Alors j'y arrive Scenario: Logged successful Given un login shingara And un mot de passe mon_password And l'utilisateur est creer When je vais sur / Then je suis redirige vers la page de login When Je me log Then Je suis redirige vers / Scenario: Je vais directement sur la page de login et j'y reste Given un login shingara And un mot de passe mon_password And l'utilisateur est creer When je vais sur /login And Je me log Then Je suis redirige vers /login