Comment tester un problème de contrôleur dans Rails 4
-
21-12-2019 - |
Question
Quelle est la meilleure façon de gérer les tests des problèmes lorsqu'ils sont utilisés dans les contrôleurs Rails 4 ?Dis que j'ai un souci trivial Citations
.
module Citations
extend ActiveSupport::Concern
def citations ; end
end
Le comportement attendu lors du test est que tout contrôleur incluant ce problème obtiendrait ce problème. citations
point final.
class ConversationController < ActionController::Base
include Citations
end
Simple.
ConversationController.new.respond_to? :yelling #=> true
Mais quelle est la bonne manière de tester cette préoccupation de manière isolée ?
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.
La solution
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 préfère effectuer des tests unitaires de manière isolée, puis utiliser des tests d'intégration pour confirmer le comportement de mes contrôleurs.
Méthode 1 :sans test de routage ni 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 :réponse aux tests
Lorsque votre problème concerne le 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 aides rspec liées 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 envelopper le contrôleur anonyme dans un controller(ApplicationController)
.Si vos classes sont héritées d'une autre classe que ApplicationController
, vous devrez l'adapter.
Aussi pour que cela fonctionne correctement vous devez déclarer dans votre spec_helper.rb déposer:
config.infer_base_class_for_anonymous_controllers = true
Note:continuez à tester que votre préoccupation est incluse
Il est également important de tester que votre classe concernée est incluse dans vos classes cibles, une ligne suffit :
describe SomeTargetedController do
describe 'includes MyControllerConcern' do
it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
end
end
Autres conseils
Simplifier 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éfinissez le type au cas où cela ne se produirait pas par défaut.
Ma réponse peut paraître 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, supprimez-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 #tap
méthode 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 un tel 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 terriblement facile de commettre une erreur de copier-coller aussi subtile.
J'utilise un moyen plus simple pour tester les problèmes de mon contrôleur, je ne sais pas si c'est la bonne méthode, mais cela me semble beaucoup plus simple que ce qui précède et cela a du sens, c'est en quelque sorte utiliser la portée de vos contrôleurs inclus.S'il vous plaît laissez-moi savoir s'il y a des problèmes avec cette méthode.contrôleur d'échantillon :
class MyController < BaseController
include MyConcern
def index
...
type = column_type(column_name)
...
end
fin
mon souci de contrôleur :
module MyConcern
...
def column_type(name)
return :phone if (column =~ /phone/).present?
return :id if column == 'id' || (column =~ /_id/).present?
:default
end
...
end
test de spécification pour préoccupation :
require 'spec_helper'
describe SearchFilter do
let(:ac) { MyController.new }
context '#column_type' do
it 'should return :phone for phone type column' do
expect(ac.column_type('phone')).to eq(:phone)
end
it 'should return :id for id column' do
expect(ac.column_type('company_id')).to eq(:id)
end
it 'should return :id for id column' do
expect(ac.column_type('id')).to eq(:id)
end
it 'should return :default for other types of columns' do
expect(ac.column_type('company_name')).to eq(:default)
end
end
end