Тестирование рендеринга заданного макета с помощью RSpec & Rails
Можно ли протестировать использование данного макета с использованием RSpec с Rails, например, я хотел бы, чтобы соответствовал следующее:
response.should use_layout('my_layout_name')
Я нашел matcher use_layout при поиске в Google, но он не работает, так как ни у ответа, ни у контроллера нет свойства макета, которое искал matcher.
11 ответов
Я нашел пример того, как написать use_layout
Matcher, который будет делать именно это. Вот код на случай, если эта ссылка исчезнет:
# 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")
Это работает для меня с пограничными Rails и пограничными RSpec на Rails:
response.layout.should == 'layouts/application'
Не должно быть трудно превратить это в подходящее для вас совпадение.
Для этого уже есть совершенно функциональный подход:
response.should render_template(:layout => 'fooo')
(Rspec 2.6.4)
Я должен был написать следующее, чтобы сделать эту работу:
response.should render_template("layouts/some_folder/some_layout", "template-name")
Вот решение, в котором я остановился. Его для rpsec 2 и рельсов 3.
Я только что добавил этот файл в каталог spec/support. Ссылка: 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
Вот обновленная версия matcher. Я обновил его, чтобы соответствовать последней версии RSpec. Я добавил соответствующие атрибуты только для чтения и удалил старый формат возврата.
# 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
Кроме того, средство сопоставления теперь также работает с макетами, указанными на уровне класса контроллера, и может использоваться следующим образом:
class PostsController < ApplicationController
layout "posts"
end
А в спецификации контроллера вы можете просто использовать:
it { should use_layout("posts") }
Должен Matchers обеспечивает соответствие для этого сценария. ( Документация) Это, кажется, работает:
expect(response).to render_with_layout('my_layout')
он генерирует соответствующие сообщения об ошибках, такие как:
Ожидается, что будет отображаться с макетом calendar_layout, но будет отображаться с помощью "application", "application"
Протестировано с rails 4.2
, rspec 3.3
а также shoulda-matchers 2.8.0
Редактировать: musta-matchers предоставляет этот метод. Shoulda:: Matchers:: ActionController:: RenderWithLayoutMatcher
response.should render_template("layouts/some_folder/some_layout")
response.should render_template("template-name")
Вот версия кода dmcnally, которая не позволяет передавать никакие аргументы, заставляя работать "should use_layout" и "should_not use_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