Skip to content

Instantly share code, notes, and snippets.

@hugobarauna
Created February 23, 2009 16:52
Show Gist options
  • Save hugobarauna/69053 to your computer and use it in GitHub Desktop.
Save hugobarauna/69053 to your computer and use it in GitHub Desktop.
module Spec
module Rails
module Example
# Controller Examples live in $RAILS_ROOT/spec/controllers/.
#
# Controller Examples use Spec::Rails::Example::ControllerExampleGroup, which supports running specs for
# Controllers in two modes, which represent the tension between the more granular
# testing common in TDD and the more high level testing built into
# rails. BDD sits somewhere in between: we want to a balance between
# specs that are close enough to the code to enable quick fault
# isolation and far enough away from the code to enable refactoring
# with minimal changes to the existing specs.
#
# == Isolation mode (default)
#
# No dependencies on views because none are ever rendered. The
# benefit of this mode is that can spec the controller completely
# independent of the view, allowing that responsibility to be
# handled later, or by somebody else. Combined w/ separate view
# specs, this also provides better fault isolation.
#
# == Integration mode
#
# To run in this mode, include the +integrate_views+ declaration
# in your controller context:
#
# describe ThingController do
# integrate_views
# ...
#
# In this mode, controller specs are run in the same way that
# rails functional tests run - one set of tests for both the
# controllers and the views. The benefit of this approach is that
# you get wider coverage from each spec. Experienced rails
# developers may find this an easier approach to begin with, however
# we encourage you to explore using the isolation mode and revel
# in its benefits.
#
# == Expecting Errors
#
# Rspec on Rails will raise errors that occur in controller actions and
# are not rescued or handeled with rescue_from.
#
class ControllerExampleGroup < FunctionalExampleGroup
class << self
# Use this to instruct RSpec to render views in your controller examples (Integration Mode).
#
# describe ThingController do
# integrate_views
# ...
#
# See Spec::Rails::Example::ControllerExampleGroup for more information about
# Integration and Isolation modes.
def integrate_views(integrate_views = true)
@integrate_views = integrate_views
end
def integrate_views? # :nodoc:
@integrate_views
end
def inherited(klass) # :nodoc:
klass.controller_class_name = controller_class_name
klass.integrate_views(integrate_views?)
super
end
# You MUST provide a controller_name within the context of
# your controller specs:
#
# describe "ThingController" do
# controller_name :thing
# ...
def controller_name(name)
@controller_class_name = "#{name}_controller".camelize
end
attr_accessor :controller_class_name # :nodoc:
end
before(:each) do
# Some Rails apps explicitly disable ActionMailer in environment.rb
if defined?(ActionMailer)
@deliveries = []
ActionMailer::Base.deliveries = @deliveries
end
unless @controller.class.ancestors.include?(ActionController::Base)
Spec::Expectations.fail_with <<-EOE
You have to declare the controller name in controller specs. For example:
describe "The ExampleController" do
controller_name "example" #invokes the ExampleController
end
EOE
end
(class << @controller; self; end).class_eval do
def controller_path #:nodoc:
self.class.name.underscore.gsub('_controller', '')
end
include ControllerInstanceMethods
end
@controller.integrate_views! if @integrate_views
@controller.session = session
end
attr_reader :response, :request, :controller
def initialize(defined_description, options={}, &implementation) #:nodoc:
super
controller_class_name = self.class.controller_class_name
if controller_class_name
@controller_class_name = controller_class_name.to_s
else
@controller_class_name = self.class.described_type.to_s
end
@integrate_views = self.class.integrate_views?
end
# Uses ActionController::Routing::Routes to generate
# the correct route for a given set of options.
# == Example
# route_for(:controller => 'registrations', :action => 'edit', :id => 1)
# => '/registrations/1;edit'
def route_for(options)
ensure_that_routes_are_loaded
ActionController::Routing::Routes.generate(options)
end
# Uses ActionController::Routing::Routes to parse
# an incoming path so the parameters it generates can be checked
# == Example
# params_from(:get, '/registrations/1;edit')
# => :controller => 'registrations', :action => 'edit', :id => 1
def params_from(method, path)
ensure_that_routes_are_loaded
ActionController::Routing::Routes.recognize_path(path, :method => method)
end
protected
def _assigns_hash_proxy
@_assigns_hash_proxy ||= AssignsHashProxy.new self do
@response.template
end
end
private
def ensure_that_routes_are_loaded
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
end
module ControllerInstanceMethods #:nodoc:
include Spec::Rails::Example::RenderObserver
# === render(options = nil, deprecated_status_or_extra_options = nil, &block)
#
# This gets added to the controller's singleton meta class,
# allowing Controller Examples to run in two modes, freely switching
# from context to context.
def render(options=nil, deprecated_status_or_extra_options=nil, &block)
if ::Rails::VERSION::STRING >= '2.0.0' && deprecated_status_or_extra_options.nil?
deprecated_status_or_extra_options = {}
end
unless block_given?
unless integrate_views?
if @template.respond_to?(:finder)
(class << @template.finder; self; end).class_eval do
define_method :file_exists? do; true; end
end
else
(class << @template; self; end).class_eval do
define_method :file_exists? do; true; end
end
end
(class << @template; self; end).class_eval do
define_method :render_file do |*args|
@first_render ||= args[0] unless args[0] =~ /^layouts/
@_first_render ||= args[0] unless args[0] =~ /^layouts/
end
define_method :_pick_template do |*args|
@_first_render ||= args[0] unless args[0] =~ /^layouts/
PickedTemplate.new
end
define_method :render do |*args|
if @_rendered
opts = args[0]
(@_rendered[:template] ||= opts[:file]) if opts[:file]
(@_rendered[:partials][opts[:partial]] += 1) if opts[:partial]
else
super
end
end
end
end
end
if matching_message_expectation_exists(options)
render_proxy.render(options, &block)
@performed_render = true
else
if matching_stub_exists(options)
@performed_render = true
else
super(options, deprecated_status_or_extra_options, &block)
end
end
end
def response(&block)
# NOTE - we're setting @update for the assert_select_spec - kinda weird, huh?
@update = block
@_response || @response
end
def integrate_views!
@integrate_views = true
end
private
def integrate_views?
@integrate_views
end
def matching_message_expectation_exists(options)
render_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
end
def matching_stub_exists(options)
render_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
end
end
Spec::Example::ExampleGroupFactory.register(:controller, self)
end
# Returned by _pick_template when running controller examples in isolation mode.
class PickedTemplate
# Do nothing when running controller examples in isolation mode.
def render_template(*ignore_args); end
# Do nothing when running controller examples in isolation mode.
def render_partial(*ignore_args); end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment