Test del rendering di un determinato layout con RSpec & amp; Rails
-
01-07-2019 - |
Domanda
È possibile testare l'uso di un determinato layout usando RSpec con Rails, ad esempio vorrei un matcher che faccia quanto segue:
response.should use_layout('my_layout_name')
Ho trovato un matcher use_layout in Google, ma non funziona poiché né la risposta né il controller sembrano avere una proprietà di layout che il matcher stava cercando.
Soluzione
Ho trovato un esempio di come scrivere un use_layout
matcher che farà proprio questo. Ecco il codice nel caso in cui quel link scompaia:
# in spec_helper.rb
class UseLayout
def initialize(expected)
@expected = 'layouts/' + expected
end
def matches?(controller)
@actual = controller.layout
#@actual.equal?(@expected)
@actual == @expected
end
def failure_message
return "use_layout expected #{@expected.inspect}, got #
{@actual.inspect}", @expected, @actual
end
def negeative_failure_message
return "use_layout expected #{@expected.inspect} not to equal #
{@actual.inspect}", @expected, @actual
end
end
def use_layout(expected)
UseLayout.new(expected)
end
# in controller spec
response.should use_layout("application")
Altri suggerimenti
David Chelimsky ha pubblicato una buona risposta sul Forum di Ruby :
response.should render_template("layouts/some_layout")
Questo funziona per me con Edge Rails e Edge RSpec su Rails:
response.layout.should == 'layouts/application'
Non dovrebbe essere difficile trasformarlo in un matcher adatto a te.
Esiste già un abbinamento perfettamente funzionale per questo:
response.should render_template(:layout => 'fooo')
(Rspec 2.6.4)
Ho dovuto scrivere quanto segue per farlo funzionare:
response.should render_template("layouts/some_folder/some_layout", "template-name")
Ecco una versione aggiornata del matcher. L'ho aggiornato per adeguarmi all'ultima versione di RSpec. Ho aggiunto gli attributi di sola lettura pertinenti e ho rimosso il vecchio formato di ritorno.
# in spec_helper.rb
class UseLayout
attr_reader :expected
attr_reader :actual
def initialize(expected)
@expected = 'layouts/' + expected
end
def matches?(controller)
if controller.is_a?(ActionController::Base)
@actual = 'layouts/' + controller.class.read_inheritable_attribute(:layout)
else
@actual = controller.layout
end
@actual ||= "layouts/application"
@actual == @expected
end
def description
"Determines if a controller uses a layout"
end
def failure_message
return "use_layout expected #{@expected.inspect}, got #{@actual.inspect}"
end
def negeative_failure_message
return "use_layout expected #{@expected.inspect} not to equal #{@actual.inspect}"
end
end
def use_layout(expected)
UseLayout.new(expected)
end
Inoltre ora il matcher funziona anche con layout specificati a livello di classe controller e può essere usato come segue:
class PostsController < ApplicationController
layout "posts"
end
E nelle specifiche del controller puoi semplicemente usare:
it { should use_layout("posts") }
Ecco la soluzione con cui ho finito. È per rpsec 2 e rotaie 3.
Ho appena aggiunto questo file nella directory spec / support.
Il link è: https://gist.github.com/971342
# spec/support/matchers/render_layout.rb
ActionView::Base.class_eval do unless instance_methods.include?('_render_layout_with_tracking') def _render_layout_with_tracking(layout, locals, &block) controller.instance_variable_set(:@_rendered_layout, layout) _render_layout_without_tracking(layout, locals, &block) end alias_method_chain :_render_layout, :tracking end end
# You can use this matcher anywhere that you have access to the controller instance, # like in controller or integration specs. # # == Example Usage # # Expects no layout to be rendered: # controller.should_not render_layout # Expects any layout to be rendered: # controller.should render_layout # Expects app/views/layouts/application.html.erb to be rendered: # controller.should render_layout('application') # Expects app/views/layouts/application.html.erb not to be rendered: # controller.should_not render_layout('application') # Expects app/views/layouts/mobile/application.html.erb to be rendered: # controller.should_not render_layout('mobile/application') RSpec::Matchers.define :render_layout do |*args| expected = args.first match do |c| actual = get_layout(c) if expected.nil? !actual.nil? # actual must be nil for the test to pass. Usage: should_not render_layout elsif actual actual == expected.to_s else false end end
failure_message_for_should do |c| actual = get_layout(c) if actual.nil? && expected.nil? "expected a layout to be rendered but none was" elsif actual.nil? "expected layout #{expected.inspect} but no layout was rendered" else "expected layout #{expected.inspect} but #{actual.inspect} was rendered" end end
failure_message_for_should_not do |c| actual = get_layout(c) if expected.nil? "expected no layout but #{actual.inspect} was rendered" else "expected #{expected.inspect} not to be rendered but it was" end end
def get_layout(controller) if template = controller.instance_variable_get(:@_rendered_layout) template.virtual_path.sub(/layouts\//, '') end end end
response.should render_template (" layouts / some_folder / some_layout ")
response.should render_template (" template-name ")
controller.active_layout.name
funziona per me.
Ecco una versione del codice di dmcnally che non consente il passaggio di argomenti, rendendo " use_layout " e " should_not use_layout " lavoro (per affermare che il controller sta usando qualsiasi layout, o nessun layout, rispettivamente - di cui mi aspetterei che solo il secondo sia utile poiché dovresti essere più specifico se sta usando un layout):
class UseLayout
def initialize(expected = nil)
if expected.nil?
@expected = nil
else
@expected = 'layouts/' + expected
end
end
def matches?(controller)
@actual = controller.layout
#@actual.equal?(@expected)
if @expected.nil?
@actual
else
@actual == @expected
end
end
def failure_message
if @expected.nil?
return 'use_layout expected a layout to be used, but none was', 'any', @actual
else
return "use_layout expected #{@expected.inspect}, got #{@actual.inspect}", @expected, @actual
end
end
def negative_failure_message
if @expected.nil?
return "use_layout expected no layout to be used, but #{@actual.inspect} found", 'any', @actual
else
return "use_layout expected #{@expected.inspect} not to equal #{@actual.inspect}", @expected, @actual
end
end
end
def use_layout(expected = nil)
UseLayout.new(expected)
end
Matcher di Shoulda fornisce un matcher per questo scenario. ( Documentation ) Questo sembra funzionare:
expect(response).to render_with_layout('my_layout')
produce messaggi di errore appropriati come:
Previsto il rendering con " calendar_layout " layout, ma renderizzato con " application " ;, " application "
Testato con rails 4.2
, rspec 3.3
e shoulda-matchers 2.8.0
Modifica: shoulda-matchers fornisce questo metodo. Shoulda :: Matchers :: :: ActionController RenderWithLayoutMatcher