So testen Sie ein Controller-Problem in Rails 4
-
21-12-2019 - |
Frage
Was ist der beste Weg, um das Testen von Bedenken bei der Verwendung in Rails 4-Controllern zu handhaben?Angenommen, ich habe ein triviales Anliegen Citations
.
module Citations
extend ActiveSupport::Concern
def citations ; end
end
Das erwartete Verhalten im Test besteht darin, dass jeder Controller, der dieses Problem aufweist, dies erhalten würde citations
Endpunkt.
class ConversationController < ActionController::Base
include Citations
end
Einfach.
ConversationController.new.respond_to? :yelling #=> true
Aber wie kann man dieses Problem isoliert testen?
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
Leider scheitert dies.
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)>'
Dies ist ein erfundenes Beispiel.In meiner App erhalte ich eine andere Fehlermeldung.
RuntimeError:
@routes is nil: make sure you set it in your test's setup method.
Lösung
Sie werden viele Ratschläge finden, die Sie dazu auffordern, gemeinsam genutzte Beispiele zu verwenden und diese im Rahmen Ihrer enthaltenen Controller auszuführen.
Ich persönlich finde es übertrieben und bevorzuge es, Unit-Tests isoliert durchzuführen, anstatt Integrationstests zu verwenden, um das Verhalten meiner Controller zu bestätigen.
Methode 1:ohne Routing- oder Antworttests
Erstellen Sie einen gefälschten Controller und testen Sie seine Methoden:
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
Methode 2:Testantwort
Wenn Ihr Problem das Routing betrifft oder Sie die Antwort, das Rendering usw. testen müssen ...Sie müssen Ihren Test mit einem anonymen Controller ausführen.Dadurch erhalten Sie Zugriff auf alle Controller-bezogenen Rspec-Methoden und -Helfer:
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
Sie können sehen, dass wir den anonymen Controller in einen einschließen müssen controller(ApplicationController)
.Wenn Ihre Klassen von einer anderen Klasse geerbt werden ApplicationController
, müssen Sie dies anpassen.
Damit dies ordnungsgemäß funktioniert, müssen Sie dies auch in Ihrem Konto angeben spec_helper.rb Datei:
config.infer_base_class_for_anonymous_controllers = true
Notiz:Testen Sie weiterhin, ob Ihr Anliegen darin enthalten ist
Es ist auch wichtig zu testen, ob Ihre Anliegenklasse in Ihren Zielklassen enthalten ist. Eine Zeile reicht aus:
describe SomeTargetedController do
describe 'includes MyControllerConcern' do
it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
end
end
Andere Tipps
Vereinfachen Sie Methode 2 anhand der Antwort mit den meisten Stimmen.
Ich bevorzuge die anonymous controller
unterstützt in rspec http://www.relishapp.com/rspec/rspec-rails/docs/controller-specs/anonymous-controller
Du wirst machen:
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
Beachten Sie, dass Sie das beschreiben müssen ApplicationController
und legen Sie den Typ fest, falls dies nicht standardmäßig geschieht.
Meine Antwort sieht vielleicht etwas komplizierter aus als die von @Benj und @Calin, aber sie hat ihre Vorteile.
describe Concerns::MyConcern, type: :controller do
described_class.tap do |mod|
controller(ActionController::Base) { include mod }
end
# your tests go here
end
Zunächst empfehle ich die Verwendung eines anonymen Controllers, der eine Unterklasse von ist ActionController::Base
, nicht ApplicationController
noch ein anderer in Ihrer Anwendung definierter Basiscontroller.Auf diese Weise können Sie das Problem isoliert von jedem Ihrer Controller testen.Wenn Sie erwarten, dass einige Methoden in einem Basiscontroller definiert werden, stubben Sie sie einfach.
Darüber hinaus ist es ratsam, den Namen des betreffenden Moduls nicht erneut einzugeben, da dadurch Fehler beim Kopieren und Einfügen vermieden werden.Bedauerlicherweise, described_class
ist in einem übergebenen Block nicht zugänglich controller(ActionController::Base)
, also benutze ich #tap
Methode zum Erstellen einer weiteren Bindung, die speichert described_class
in einer lokalen Variablen.Dies ist besonders wichtig, wenn Sie mit versionierten APIs arbeiten.In einem solchen Fall ist es durchaus üblich, beim Erstellen einer neuen Version eine große Anzahl von Controllern zu kopieren, und es ist dann furchtbar leicht, einen so subtilen Fehler beim Kopieren und Einfügen zu machen.
Ich verwende eine einfachere Methode, um meine Controller-Bedenken zu testen. Ich bin mir nicht sicher, ob dies die richtige Methode ist, aber sie erschien mir viel einfacher als die oben genannte und macht für mich Sinn, da sie den Umfang Ihrer mitgelieferten Controller nutzt.Bitte lassen Sie mich wissen, wenn es Probleme mit dieser Methode gibt.Probencontroller:
class MyController < BaseController
include MyConcern
def index
...
type = column_type(column_name)
...
end
Ende
Mein Controller-Anliegen:
module MyConcern
...
def column_type(name)
return :phone if (column =~ /phone/).present?
return :id if column == 'id' || (column =~ /_id/).present?
:default
end
...
end
Spezifikationstest zur Besorgnis:
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