45 votes

Comment écrire un test de contrôleur de moteur Rails 3.1 dans rspec ?

J'ai écrit un moteur Rails 3.1 avec l'espace de nom Posts. Ainsi, mes contrôleurs se trouvent dans app/controllers/posts/, mes modèles dans app/models/posts, etc. Je peux tester les modèles sans problème. La spécification d'un modèle ressemble à...

module Posts
  describe Post do
    describe 'Associations' do
      it ...
      end

... et tout fonctionne bien.

Cependant, les spécifications des contrôleurs ne fonctionnent pas. Le moteur Rails est monté à /posts, mais le contrôleur est Posts::PostController. Ainsi, les tests cherchent à ce que la route du contrôleur soit posts/posts.

  describe "GET index" do
    it "assigns all posts as @posts" do
      Posts::Post.stub(:all) { [mock_post] }
       get :index
       assigns(:posts).should eq([mock_post])
    end
  end

ce qui donne...

  1) Posts::PostsController GET index assigns all posts as @posts
     Failure/Error: get :index
     ActionController::RoutingError:
     No route matches {:controller=>"posts/posts"}
     # ./spec/controllers/posts/posts_controller_spec.rb:16

J'ai essayé toutes sortes d'astuces dans le fichier routes de l'application de test... :namespace, etc, sans succès.

Comment faire pour que ça marche ? Il semble que ce ne soit pas le cas, puisque le moteur place le contrôleur dans /posts, alors que l'espacement des noms place le contrôleur dans /posts/posts pour les besoins du test.

42voto

Benoit Garret Points 8292

Je suppose que vous testez votre moteur avec une application rails factice, comme celle qui serait générée par moteurx .

Votre moteur doit être monté dans l'application fictive :

Sur spec/dummy/config/routes.rb :

Dummy::Application.routes.draw do
  mount Posts::Engine => '/posts-prefix'
end

Ma deuxième hypothèse est que votre moteur est isolé :

Sur lib/posts.rb :

module Posts
  class Engine < Rails::Engine
    isolate_namespace Posts
  end
end

Je ne sais pas si ces deux hypothèses sont vraiment nécessaires, mais c'est ainsi que mon propre moteur est structuré.

La solution de contournement est assez simple, au lieu de ceci

get :show, :id => 1

utiliser ce

get :show, {:id => 1, :use_route => :posts}

Le site :posts doit être le nom de votre moteur et NON le chemin où il est monté.

Cela fonctionne car les paramètres de la méthode get sont passés directement à la méthode ActionDispatch::Routing::RouteSet::Generator#initialize (défini ici ), qui utilise à son tour @named_route pour obtenir l'itinéraire correct de Rack::Mount::RouteSet#generate (voir ici et ici ).

Se plonger dans l'intérieur des rails est amusant, mais prend beaucoup de temps, je ne le ferais pas tous les jours ;-) .

HTH

0 votes

Cela m'a beaucoup aidé. Merci !

2 votes

Je me demande s'il ne serait pas possible de définir ce paramètre comme une sorte de global quelque part afin qu'il n'ait pas à être transmis à chacun des tests...

1 votes

J'aimerais aussi savoir comment vous avez appris cela.

22voto

Ryan Bigg Points 64561

J'ai contourné ce problème en surchargeant la fonction get , post , put et delete qui sont fournies, en faisant en sorte qu'ils passent toujours use_route en tant que paramètre.

J'ai utilisé la réponse de Benoît comme base pour cela. Merci mon pote !

module ControllerHacks
  def get(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "GET")
  end

  # Executes a request simulating POST HTTP method and set/volley the response
  def post(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "POST")
  end

  # Executes a request simulating PUT HTTP method and set/volley the response
  def put(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "PUT")
  end

  # Executes a request simulating DELETE HTTP method and set/volley the response
  def delete(action, parameters = nil, session = nil, flash = nil)
    process_action(action, parameters, session, flash, "DELETE")
  end

  private

  def process_action(action, parameters = nil, session = nil, flash = nil, method = "GET")
    parameters ||= {}
    process(action, parameters.merge!(:use_route => :my_engine), session, flash, method)
  end
end

RSpec.configure do |c|
  c.include ControllerHacks, :type => :controller
end

0 votes

C'est beaucoup mieux que de spécifier cela à chaque fois, ajouté aux favoris, merci !

0 votes

Merci beaucoup Ryan pour ça. Cela fonctionne comme un charme. Je l'utiliserai jusqu'à ce qu'une approche encore plus simple se présente.

0 votes

@Inc1982 : Pas d'inquiétude. Je suis heureux d'aider !

19voto

Mars Points 1061

Utilisez l'outil rspec-rails routes directive :

describe MyEngine::WidgetsController do
  routes { MyEngine::Engine.routes }

  # Specs can use the engine's routes & named URL helpers
  # without any other special code.
end

- Documentation officielle de RSpec Rails 2.14 .

5voto

wintersolutions Points 2526

Sur la base de ce réponse, j'ai choisi la solution suivante :

#spec/spec_helper.rb
RSpec.configure do |config|
 # other code
 config.before(:each) { @routes = UserManager::Engine.routes }
end

Le site avantage supplémentaire est, que vous n'avez pas besoin d'avoir le before(:each) dans chaque spécification de contrôleur.

0 votes

Cela ne semble plus fonctionner. J'avais la même idée de solution, mais cela ne semble pas faire de différence.

0 votes

Même si cela fonctionnait, vous briseriez tous vos autres tests qui sont pour le reste de votre application.

2voto

Spajus Points 3296

Solution pour un problème lorsque vous n'avez pas ou ne pouvez pas utiliser isolate_namespace :

module Posts
  class Engine < Rails::Engine
  end
end

Dans les spécifications du contrôleur, pour fixer les itinéraires :

get :show, {:id => 1, :use_route => :posts_engine}   

Rails ajoute _engine aux routes de votre application si vous n'utilisez pas la fonction isolate_namespace .

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