Created
February 23, 2009 16:52
-
-
Save hugobarauna/69053 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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