Skip to content

Commit

Permalink
include ActionController::TestCase::Behavior
Browse files Browse the repository at this point in the history
- this includes patches sumbitted to rails, supporting new behavior
  with rails-3.0.0.beta.3 and future releases as well
  - https://rails.lighthouseapp.com/projects/8994/tickets/4433 (merged)
  - http://rails.lighthouseapp.com/projects/8994/tickets/4474 (pending)
  • Loading branch information
dchelimsky committed Apr 25, 2010
1 parent 9d450d8 commit 5ec35fd
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 66 deletions.
1 change: 1 addition & 0 deletions lib/rspec/rails.rb
@@ -1,3 +1,4 @@
require 'rspec/rails/monkey'
require 'rspec/rails/transactional_database_support'
require 'rspec/rails/matchers'
require 'rspec/rails/example'
Expand Down
82 changes: 16 additions & 66 deletions lib/rspec/rails/example/controller_example_group.rb
Expand Up @@ -2,92 +2,42 @@
require 'action_controller'
require 'action_dispatch'
require 'webrat'
require 'test/unit/assertions'

# BEGIN PATCH
#
# The following monkey patches to rails can be removed if/when
# https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4433
# is resolved.
require 'active_support/notifications/fanout'
class ActiveSupport::Notifications::Fanout
def unsubscribe(subscriber_or_name)
@listeners_for.clear
@subscribers.reject! do |s|
s.instance_eval do
case subscriber_or_name
when String
@pattern && @pattern =~ subscriber_or_name
when self
true
end
end
end
module Rspec::Rails::ActiveSupportConcernAdapter
def setup(*methods)
methods.each {|method| before { send method } }
end
end

require 'action_controller/test_case'
module ActionController::TemplateAssertions
def teardown_subscriptions
ActiveSupport::Notifications.unsubscribe("action_view.render_template")
ActiveSupport::Notifications.unsubscribe("action_view.render_template!")
def teardown(*methods)
methods.each {|method| after { send method } }
end
end
# END PATCH

# Preliminary documentation (more to come ....):
#
# allow_forgery_protection is set to false
# - you can set it to true in a before(:each) block
# if you have a specific example that needs it, but
# be sure to restore it to false (or supply tokens
# to all of your example requests)

module ControllerExampleGroupBehaviour
include ActionDispatch::Assertions
include ActionDispatch::Integration::Runner
include Test::Unit::Assertions
include Webrat::Matchers
include Webrat::Methods
include Rspec::Matchers

module ActiveSupportConcernAdapter
def setup(*methods)
methods.each {|method| before { send method } }
end
def self.included(mod)
mod.extend Rspec::Rails::ActiveSupportConcernAdapter
mod.__send__ :include, ActionController::TestCase::Behavior

def teardown(*methods)
methods.each {|method| after { send method } }
def mod.controller_class
describes
end
end

def self.included(mod)
mod.extend ActiveSupportConcernAdapter

mod.before do
@routes = Rails.application.routes
@_result = Struct.new(:add_assertion).new
ActionController::Base.allow_forgery_protection = false
end
end

def app
described_class.action(@_action).tap do |endpoint|
def endpoint.routes
Rails.application.routes
end
end
end

%w[get post put delete head].map do |method|
eval <<-CODE
def #{method}(*args)
@_action = args.shift
super '/', *args
end
CODE
end
end

Rspec.configure do |c|
[ControllerExampleGroupBehaviour, ActionController::TemplateAssertions].each do |mod|
c.include mod, :example_group => { :file_path => /\bspec\/controllers\// }
end
c.include ControllerExampleGroupBehaviour, :example_group => {
:describes => lambda {|described| described < ActionController::Base }
}
end
2 changes: 2 additions & 0 deletions lib/rspec/rails/monkey.rb
@@ -0,0 +1,2 @@
require 'rspec/rails/monkey/action_controller/test_case'
require 'rspec/rails/monkey/active_support/notifications/fanout'
198 changes: 198 additions & 0 deletions lib/rspec/rails/monkey/action_controller/test_case.rb
@@ -0,0 +1,198 @@
require 'action_controller/test_case'

module ActionController
# This has been merged to rails HEAD after the 3.0.0.beta.3 release (see
# https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4433).
# Once 3.0.0.rc.1 comes out, we can remove it.
module TemplateAssertions
def teardown_subscriptions
ActiveSupport::Notifications.unsubscribe("action_view.render_template")
ActiveSupport::Notifications.unsubscribe("action_view.render_template!")
end
end

# The remainder of this file has yet to be merged to rails HEAD and is
# therefore merely speculative and hopeful.
class TestCase < ActiveSupport::TestCase
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::TestProcess

attr_reader :response, :request

module ClassMethods

# Sets the controller class name. Useful if the name can't be inferred from test class.
# Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
def tests(controller_class)
self.controller_class = controller_class
end

def controller_class=(new_class)
prepare_controller_class(new_class) if new_class
write_inheritable_attribute(:controller_class, new_class)
end

def controller_class
if current_controller_class = read_inheritable_attribute(:controller_class)
current_controller_class
else
self.controller_class = determine_default_controller_class(name)
end
end

def determine_default_controller_class(name)
name.sub(/Test$/, '').constantize
rescue NameError
nil
end

def prepare_controller_class(new_class)
new_class.send :include, ActionController::TestCase::RaiseActionExceptions
end

end

# Executes a request simulating GET HTTP method and set/volley the response
def get(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "GET")
end

# Executes a request simulating POST HTTP method and set/volley the response
def post(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "POST")
end

# Executes a request simulating PUT HTTP method and set/volley the response
def put(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "PUT")
end

# Executes a request simulating DELETE HTTP method and set/volley the response
def delete(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "DELETE")
end

# Executes a request simulating HEAD HTTP method and set/volley the response
def head(action, parameters = nil, session = nil, flash = nil)
process(action, parameters, session, flash, "HEAD")
end

def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
returning __send__(request_method, action, parameters, session, flash) do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
end
alias xhr :xml_http_request

def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
# Sanity check for required instance variables so we can give an
# understandable error message.
%w(@routes @controller @request @response).each do |iv_name|
if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
end
end

@request.recycle!
@response.recycle!
@controller.response_body = nil
@controller.formats = nil
@controller.params = nil

@html_document = nil
@request.env['REQUEST_METHOD'] = http_method

parameters ||= {}
@request.assign_parameters(@routes, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)

@request.session = ActionController::TestSession.new(session) unless session.nil?
@request.session["flash"] = @request.flash.update(flash || {})
@request.session["flash"].sweep

@controller.request = @request
@controller.params.merge!(parameters)
build_request_uri(action, parameters)
Base.class_eval { include Testing }
@controller.process_with_new_base_test(@request, @response)
@request.session.delete('flash') if @request.session['flash'].blank?
@response
end

def setup_controller_request_and_response
@request = TestRequest.new
@response = TestResponse.new

if klass = self.class.controller_class
@controller ||= klass.new rescue nil
end

@request.env.delete('PATH_INFO')

if @controller
@controller.request = @request
@controller.params = {}
end
end

# Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
def rescue_action_in_public!
@request.remote_addr = '208.77.188.166' # example.com
end

included do
include ActionController::TemplateAssertions
include ActionDispatch::Assertions
setup :setup_controller_request_and_response
end

private

def build_request_uri(action, parameters)
unless @request.env["PATH_INFO"]
options = @controller.__send__(:url_options).merge(parameters)
options.update(
:only_path => true,
:action => action,
:relative_url_root => nil,
:_path_segments => @request.symbolized_path_parameters)

url, query_string = @routes.url_for(options).split("?", 2)

@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
@request.env["PATH_INFO"] = url
@request.env["QUERY_STRING"] = query_string || ""
end
end
end

# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
# than 0.0.0.0.
#
# The exception is stored in the exception accessor for further inspection.
module RaiseActionExceptions
def self.included(base)
base.class_eval do
attr_accessor :exception
protected :exception, :exception=
end
end

protected
def rescue_action_without_handler(e)
self.exception = e

if request.remote_addr == "0.0.0.0"
raise(e)
else
super(e)
end
end
end
end
end
20 changes: 20 additions & 0 deletions lib/rspec/rails/monkey/active_support/notifications/fanout.rb
@@ -0,0 +1,20 @@
require 'active_support/notifications/fanout'

# This has been merged to rails HEAD after the 3.0.0.beta.3 release (see
# https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4433).
# Once 3.0.0.rc.1 comes out, we can remove it.
class ActiveSupport::Notifications::Fanout
def unsubscribe(subscriber_or_name)
@listeners_for.clear
@subscribers.reject! do |s|
s.instance_eval do
case subscriber_or_name
when String
@pattern && @pattern =~ subscriber_or_name
when self
true
end
end
end
end
end

0 comments on commit 5ec35fd

Please sign in to comment.