Quelle est la meilleure façon de gérer les tests de problèmes lorsqu'il est utilisé dans Rails 4 contrôleurs? Disons que j'ai une préoccupation triviale Citations
.
module Citations
extend ActiveSupport::Concern
def citations ; end
end
Le comportement attendu sous test est que tout contrôleur qui inclut cette préoccupation obtiendrait ce point de terminaison citations
.
class ConversationController < ActionController::Base
include Citations
end
Facile.
ConversationController.new.respond_to? :yelling #=> true
Mais quelle est la bonne façon de tester cette préoccupation isolément?
class CitationConcernController < ActionController::Base
include Citations
end
describe CitationConcernController, type: :controller do
it 'should add the citations endpoint' do
get :citations
expect(response).to be_successful
end
end
Malheureusement, cela échoue.
CitationConcernController
should add the citations endpoint (FAILED - 1)
Failures:
1) CitationConcernController should add the citations endpoint
Failure/Error: get :citations
ActionController::UrlGenerationError:
No route matches {:controller=>"citation_concern", :action=>"citations"}
# ./controller_concern_spec.rb:14:in `block (2 levels) in <top (required)>'
Ceci est un exemple artificiel. Dans mon application, j'obtiens une erreur différente.
RuntimeError:
@routes is nil: make sure you set it in your test's setup method.
Vous trouverez de nombreux conseils vous indiquant d'utiliser des exemples partagés et de les exécuter dans le cadre de vos contrôleurs inclus.
Personnellement, je trouve cela excessif et je préfère effectuer des tests unitaires isolément, puis utiliser des tests d'intégration pour confirmer le comportement de mes contrôleurs.
Méthode 1: sans routage ni test de réponse
Créez un faux contrôleur et testez ses méthodes:
describe MyControllerConcern do
before do
class FakesController < ApplicationController
include MyControllerConcern
end
end
after { Object.send :remove_const, :FakesController }
let(:object) { FakesController.new }
describe 'my_method_to_test' do
it { expect(object).to eq('expected result') }
end
end
Méthode 2: tester la réponse
Lorsque votre problème contient un routage ou que vous devez tester la réponse, le rendu, etc., vous devez exécuter votre test avec un contrôleur anonyme. Cela vous permet d'accéder à toutes les méthodes et assistants rspec liés au contrôleur:
describe MyControllerConcern, type: :controller do
controller(ApplicationController) do
include MyControllerConcern
def fake_action; redirect_to '/an_url'; end
end
before { routes.draw {
get 'fake_action' => 'anonymous#fake_action'
} }
describe 'my_method_to_test' do
before { get :fake_action }
it { expect(response).to redirect_to('/an_url') }
end
end
Vous pouvez voir que nous devons encapsuler le contrôleur anonyme dans une controller(ApplicationController)
. Si vos classes sont héritées d'une autre classe que ApplicationController
, vous devrez l'adapter.
De plus, pour que cela fonctionne correctement, vous devez déclarer dans votre fichier spec_helper.rb :
config.infer_base_class_for_anonymous_controllers = true
Remarque: continuez à tester que votre préoccupation est incluse
Il est également important de tester que votre classe de préoccupation est incluse dans vos classes cibles, une seule ligne suffit:
describe SomeTargetedController do
describe 'includes MyControllerConcern' do
it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
end
end
Simplification de la méthode 2 à partir de la réponse la plus votée.
Je préfère le anonymous controller
pris en charge dans rspec http://www.relishapp.com/rspec/rspec-Rails/docs/controller-specs/anonymous-controller
Vous ferez:
describe ApplicationController, type: :controller do
controller do
include MyControllerConcern
def index; end
end
describe 'GET index' do
it 'will work' do
get :index
end
end
end
Notez que vous devez décrire le ApplicationController
et définir le type au cas où cela ne se produirait pas par défaut.
Ma réponse peut sembler un peu plus compliquée que celles de @Benj et @Calin, mais elle a ses avantages.
describe Concerns::MyConcern, type: :controller do
described_class.tap do |mod|
controller(ActionController::Base) { include mod }
end
# your tests go here
end
Tout d'abord, je recommande l'utilisation d'un contrôleur anonyme qui est une sous-classe de ActionController::Base
, Pas ApplicationController
ni aucun autre contrôleur de base défini dans votre application. De cette façon, vous pouvez tester le problème indépendamment de n'importe lequel de vos contrôleurs. Si vous vous attendez à ce que certaines méthodes soient définies dans un contrôleur de base, écrivez-les simplement.
De plus, c'est une bonne idée d'éviter de retaper le nom du module concerné car cela permet d'éviter les erreurs de copier-coller. Malheureusement, described_class
N'est pas accessible dans un bloc passé à controller(ActionController::Base)
, donc j'utilise la méthode #tap
Pour créer une autre liaison qui stocke described_class
Dans une variable locale . Ceci est particulièrement important lorsque vous travaillez avec des API versionnées. Dans ce cas, il est assez courant de copier un grand volume de contrôleurs lors de la création d'une nouvelle version, et il est alors extrêmement facile de faire une erreur de copier-coller si subtile.