Come testare un Controller Concern in Rails 4
-
21-12-2019 - |
Domanda
Qual è il modo migliore per gestire i test problematici quando utilizzati nei controller Rails 4?Diciamo che ho una preoccupazione banale Citations
.
module Citations
extend ActiveSupport::Concern
def citations ; end
end
Il comportamento previsto durante il test è che qualsiasi controller che includa questo problema lo otterrebbe citations
punto finale.
class ConversationController < ActionController::Base
include Citations
end
Semplice.
ConversationController.new.respond_to? :yelling #=> true
Ma qual è il modo giusto per testare questa preoccupazione isolatamente?
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
Sfortunatamente, questo fallisce.
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)>'
Questo è un esempio inventato.Nella mia app, ricevo un errore diverso.
RuntimeError:
@routes is nil: make sure you set it in your test's setup method.
Soluzione
Troverai molti consigli che ti dicono di utilizzare esempi condivisi ed eseguirli nell'ambito dei controller inclusi.
Personalmente lo trovo eccessivo e preferisco eseguire test unitari in isolamento, quindi utilizzare test di integrazione per confermare il comportamento dei miei controller.
Metodo 1:senza routing o test di risposta
Crea un controller falso e testa i suoi metodi:
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
Metodo 2:testare la risposta
Quando la tua preoccupazione riguarda il routing o devi testare la risposta, il rendering ecc...devi eseguire il test con un controller anonimo.Ciò ti consente di accedere a tutti i metodi e gli helper rspec relativi al controller:
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
Puoi vedere che dobbiamo racchiudere il controller anonimo in a controller(ApplicationController)
.Se le tue classi sono ereditate da un'altra classe rispetto a ApplicationController
, dovrai adattarlo.
Anche perché questo funzioni correttamente devi dichiararlo nel tuo spec_helper.rb file:
config.infer_base_class_for_anonymous_controllers = true
Nota:continua a verificare che la tua preoccupazione sia inclusa
È anche importante verificare che la classe di interesse sia inclusa nelle classi target, è sufficiente una riga:
describe SomeTargetedController do
describe 'includes MyControllerConcern' do
it { expect(SomeTargetedController.ancestors.include? MyControllerConcern).to eq(true) }
end
end
Altri suggerimenti
Simplicare sul metodo 2 dalla risposta più votata.
Preferisco il anonymous controller
supportato in Rspec http://www.relishApp.COM / RSPEC / RSPEC-Rails / Docs / Controller-Specifiche / Controller anonimo
lo farai:
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
.
Nota che è necessario descrivere il ApplicationController
e impostare il tipo nel caso in cui ciò non accada per impostazione predefinita.
La mia risposta potrebbe sembrare un po 'più complicata di questi di @benj e @calin, ma ha i suoi vantaggi.
describe Concerns::MyConcern, type: :controller do
described_class.tap do |mod|
controller(ActionController::Base) { include mod }
end
# your tests go here
end
.
Prima di tutto, raccomando l'uso di controller anonimo che è una sottoclasse di ActionController::Base
, non ApplicationController
né qualsiasi altro controller di base definito nell'applicazione. In questo modo sei in grado di testare la preoccupazione in isolamento da qualsiasi dei tuoi controller. Se ti aspetti che alcuni metodi vengano definiti in un controller di base, solo stubli.
Inoltre, è una buona idea evitare di ri-digitare il nome del modulo del modulo in quanto aiuta a evitare errori di copia-incolla. Sfortunatamente, described_class
non è accessibile in un blocco passato a controller(ActionController::Base)
, quindi utilizzo il metodo #tap
per creare un altro legame che memorizza described_class
in una variabile locale. Questo è particolarmente importante quando si lavora con le API con versione. In tal caso è abbastanza comune copiare un grande volume di controllori durante la creazione di una nuova versione, ed è terribilmente facile fare un errore di pasta di copia così sottile.
Sto usando un modo più semplice per testare i miei problemi di controllo, non sono sicuro se questo è il modo corretto, ma sembrava molto più semplice che quanto sopra e abbia senso per me, il suo tipo di utilizzo del campo di applicazione dei controller inclusi.Per favore fatemi sapere se ci sono problemi con questo metodo. Controller di esempio:
class MyController < BaseController
include MyConcern
def index
...
type = column_type(column_name)
...
end
.
fine
Il mio controller riguarda:
module MyConcern
...
def column_type(name)
return :phone if (column =~ /phone/).present?
return :id if column == 'id' || (column =~ /_id/).present?
:default
end
...
end
.
Test delle specifiche per preoccupazione:
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
.