views:

2151

answers:

7

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  A: 

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")
Otto
That's the same one that I found, and from my tests it didn't seem to work as there is no layout attribute on the response (or the controller) from what I can see.
DEfusion
Ah it seems there is a layout method, but it takes some arguments. I'll ponder on what the solution probably is.
Otto
On Rails 2.2.2 now and this works fine
DEfusion
+6  A: 

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.

mislav
Also works for me.
Otto
A: 

response.layout is coming back nil for me unless the action explicitly calls render :layout => "layout"

using rails 2.3.2 and rspec 1.2.4

Anybody have a solution/workaround for this?

+2  A: 

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") }
dmcnally
A: 

controller.active_layout.name works for me

A: 

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
mBread
+1  A: 

David Chelimsky posted a good answer over on the Ruby Forum:

response.should render_template("layouts/some_layout")
Kevin Ansfield