可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is it possible to test the use of a given layout using RSpec with Rails, for example I'd like a matcher that does the following:
response.should use_layout('my_layout_name')
I found a use_layout matcher when Googling but it doesn't work as neither the response or controller seem to have a layout property that matcher was looking for.
回答1:
I found an example of how to write a use_layout
matcher that will do just that. Here's the code in case that link goes away:
# 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")
回答2:
David Chelimsky posted a good answer over on the Ruby Forum:
response.should render_template("layouts/some_layout")
回答3:
This works for me with edge Rails and edge RSpec on Rails:
response.layout.should == 'layouts/application'
Shouldn't be hard to turn this into a matcher suitable for you.
回答4:
There's already a perfectly functional matcher for this:
response.should render_template(:layout => 'fooo')
(Rspec 2.6.4)
回答5:
I had to write the following to make this work:
response.should render_template("layouts/some_folder/some_layout", "template-name")
回答6:
Here is an updated version of the matcher. I've updated it to conform to the latest version of RSpec. I've added the relevant read only attributes and remove old return format.
# 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
Additionally the matcher now also works with layouts specified at the controller class level and can be used as follows:
class PostsController < ApplicationController
layout "posts"
end
And in the controller spec you can simply use:
it { should use_layout("posts") }
回答7:
Here's the solution I ended up going with. Its for rpsec 2 and rails 3.
I just added this file in the spec/support directory.
The link is: 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
回答8:
response.should render_template("layouts/some_folder/some_layout")
response.should render_template("template-name")
回答9:
controller.active_layout.name
works for me.
回答10:
Shoulda Matchers provides a matcher for this scenario. (Documentation)
This seems to work:
expect(response).to render_with_layout('my_layout')
it produces appropriate failure messages like:
Expected to render with the "calendar_layout" layout, but rendered with "application", "application"
Tested with rails 4.2
, rspec 3.3
and shoulda-matchers 2.8.0
Edit: shoulda-matchers provides this method. Shoulda::Matchers::ActionController::RenderWithLayoutMatcher
回答11:
Here's a version of dmcnally's code that allows no arguments to be passed, making "should use_layout" and "should_not use_layout" work (to assert that the controller is using any layout, or no layout, respectively - of which I would expect only the second to be useful as you should be more specific if it is using a 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