Added Rspec and Webrat plugins and started porting Selenium on Rails tests to Rspec Plain Text Stories driving Webrat driving Selenium.

This commit is contained in:
Luke Melia 2008-06-18 02:57:57 -04:00 committed by Simon Rozet
parent 7b432a74ed
commit 2c09db45c5
602 changed files with 47788 additions and 29 deletions

View file

@ -0,0 +1,46 @@
dir = File.dirname(__FILE__)
require 'spec/rails/example/assigns_hash_proxy'
require "spec/rails/example/render_observer"
require "spec/rails/example/rails_example_group"
require "spec/rails/example/model_example_group"
require "spec/rails/example/functional_example_group"
require "spec/rails/example/controller_example_group"
require "spec/rails/example/helper_example_group"
require "spec/rails/example/view_example_group"
module Spec
module Rails
# Spec::Rails::Example extends Spec::Example (RSpec's core Example module) to provide
# Rails-specific contexts for describing Rails Models, Views, Controllers and Helpers.
#
# == Model Examples
#
# These are the equivalent of unit tests in Rails' built in testing. Ironically (for the traditional TDD'er) these are the only specs that we feel should actually interact with the database.
#
# See Spec::Rails::Example::ModelExampleGroup
#
# == Controller Examples
#
# These align somewhat with functional tests in rails, except that they do not actually render views (though you can force rendering of views if you prefer). Instead of setting expectations about what goes on a page, you set expectations about what templates get rendered.
#
# See Spec::Rails::Example::ControllerExampleGroup
#
# == View Examples
#
# This is the other half of Rails functional testing. View specs allow you to set up assigns and render
# a template. By assigning mock model data, you can specify view behaviour with no dependency on a database
# or your real models.
#
# See Spec::Rails::Example::ViewExampleGroup
#
# == Helper Examples
#
# These let you specify directly methods that live in your helpers.
#
# See Spec::Rails::Example::HelperExampleGroup
module Example
end
end
end

View file

@ -0,0 +1,43 @@
module Spec
module Rails
module Example
class AssignsHashProxy #:nodoc:
def initialize(object)
@object = object
end
def [](ivar)
if assigns.include?(ivar.to_s)
assigns[ivar.to_s]
elsif assigns.include?(ivar)
assigns[ivar]
else
nil
end
end
def []=(ivar, val)
@object.instance_variable_set "@#{ivar}", val
assigns[ivar.to_s] = val
end
def delete(name)
assigns.delete(name.to_s)
end
def each(&block)
assigns.each &block
end
def has_key?(key)
assigns.key?(key.to_s)
end
protected
def assigns
@object.assigns
end
end
end
end
end

View file

@ -0,0 +1,271 @@
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.
# In contrast, Rails will swallow errors that are raised in controller
# actions and return an error code in the header. If you wish to override
# Rspec and have Rail's default behaviour,tell the controller to use
# rails error handling ...
#
# before(:each) do
# controller.use_rails_error_handling!
# end
#
# When using Rail's error handling, you can expect error codes in headers ...
#
# it "should return an error in the header" do
# response.should be_error
# end
#
# it "should return a 501" do
# response.response_code.should == 501
# end
#
# it "should return a 501" do
# response.code.should == "501"
# end
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, &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 @controller
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]
end
end
end
end
if matching_message_expectation_exists(options)
expect_render_mock_proxy.render(options, &block)
@performed_render = true
else
unless matching_stub_exists(options)
super(options, deprecated_status_or_extra_options, &block)
end
end
end
def raise_with_disable_message(old_method, new_method)
raise %Q|
controller.#{old_method}(:render) has been disabled because it
can often produce unexpected results. Instead, you should
use the following (before the action):
controller.#{new_method}(*args)
See the rdoc for #{new_method} for more information.
|
end
def should_receive(*args)
if args[0] == :render
raise_with_disable_message("should_receive", "expect_render")
else
super
end
end
def stub!(*args)
if args[0] == :render
raise_with_disable_message("stub!", "stub_render")
else
super
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)
expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
end
def matching_stub_exists(options)
expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
end
end
Spec::Example::ExampleGroupFactory.register(:controller, self)
end
end
end
end

View file

@ -0,0 +1,59 @@
module Spec
module Rails
module Example
class FunctionalExampleGroup < RailsExampleGroup
include ActionController::TestProcess
include ActionController::Assertions
attr_reader :request, :response
before(:each) do
@controller_class = Object.path2class @controller_class_name
raise "Can't determine controller class for #{@controller_class_name}" if @controller_class.nil?
@controller = @controller_class.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@response.session = @request.session
end
def params
request.parameters
end
def flash
response.flash
end
def session
response.session
end
# :call-seq:
# assigns()
#
# Hash of instance variables to values that are made available to
# views. == Examples
#
# #in thing_controller.rb
# def new
# @thing = Thing.new
# end
#
# #in thing_controller_spec
# get 'new'
# assigns[:registration].should == Thing.new
#--
# NOTE - Even though docs only use assigns[:key] format, this supports
# assigns(:key) in order to avoid breaking old specs.
#++
def assigns(key = nil)
if key.nil?
_assigns_hash_proxy
else
_assigns_hash_proxy[key]
end
end
end
end
end
end

View file

@ -0,0 +1,164 @@
module Spec
module Rails
module Example
# Helper Specs live in $RAILS_ROOT/spec/helpers/.
#
# Helper Specs use Spec::Rails::Example::HelperExampleGroup, which allows you to
# include your Helper directly in the context and write specs directly
# against its methods.
#
# HelperExampleGroup also includes the standard lot of ActionView::Helpers in case your
# helpers rely on any of those.
#
# == Example
#
# class ThingHelper
# def number_of_things
# Thing.count
# end
# end
#
# describe "ThingHelper example_group" do
# include ThingHelper
# it "should tell you the number of things" do
# Thing.should_receive(:count).and_return(37)
# number_of_things.should == 37
# end
# end
class HelperExampleGroup < FunctionalExampleGroup
class HelperObject < ActionView::Base
def protect_against_forgery?
false
end
def session=(session)
@session = session
end
def request=(request)
@request = request
end
def flash=(flash)
@flash = flash
end
def params=(params)
@params = params
end
def controller=(controller)
@controller = controller
end
private
attr_reader :session, :request, :flash, :params, :controller
end
class << self
# The helper name....
def helper_name(name=nil)
@helper_being_described = "#{name}_helper".camelize.constantize
send :include, @helper_being_described
end
def helper
@helper_object ||= returning HelperObject.new do |helper_object|
if @helper_being_described.nil?
if described_type.class == Module
helper_object.extend described_type
end
else
helper_object.extend @helper_being_described
end
end
end
end
# Returns an instance of ActionView::Base with the helper being spec'd
# included.
#
# == Example
#
# describe PersonHelper do
# it "should write a link to person with the name" do
# assigns[:person] = mock_model(Person, :full_name => "Full Name", :id => 37, :new_record? => false)
# helper.link_to_person.should == %{<a href="/people/37">Full Name</a>}
# end
# end
#
# module PersonHelper
# def link_to_person
# link_to person.full_name, url_for(person)
# end
# end
#
def helper
self.class.helper
end
# Reverse the load order so that custom helpers which are defined last
# are also loaded last.
ActionView::Base.included_modules.reverse.each do |mod|
include mod if mod.parents.include?(ActionView::Helpers)
end
before(:all) do
@controller_class_name = 'Spec::Rails::Example::HelperExampleGroupController'
end
before(:each) do
@controller.request = @request
@controller.url = ActionController::UrlRewriter.new @request, {} # url_for
@flash = ActionController::Flash::FlashHash.new
session['flash'] = @flash
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
helper.session = session
helper.request = @request
helper.flash = flash
helper.params = params
helper.controller = @controller
end
def flash
@flash
end
def eval_erb(text)
erb_args = [text]
if helper.respond_to?(:output_buffer)
erb_args += [nil, nil, '@output_buffer']
end
helper.instance_eval do
ERB.new(*erb_args).result(binding)
end
end
# TODO: BT - Helper Examples should proxy method_missing to a Rails View instance.
# When that is done, remove this method
def protect_against_forgery?
false
end
Spec::Example::ExampleGroupFactory.register(:helper, self)
protected
def _assigns_hash_proxy
@_assigns_hash_proxy ||= AssignsHashProxy.new helper
end
end
class HelperExampleGroupController < ApplicationController #:nodoc:
attr_accessor :request, :url
# Re-raise errors
def rescue_action(e); raise e; end
end
end
end
end

View file

@ -0,0 +1,14 @@
module Spec
module Rails
module Example
# Model examples live in $RAILS_ROOT/spec/models/.
#
# Model examples use Spec::Rails::Example::ModelExampleGroup, which
# provides support for fixtures and some custom expectations via extensions
# to ActiveRecord::Base.
class ModelExampleGroup < RailsExampleGroup
Spec::Example::ExampleGroupFactory.register(:model, self)
end
end
end
end

View file

@ -0,0 +1,29 @@
require 'spec/interop/test'
if ActionView::Base.respond_to?(:cache_template_extension)
ActionView::Base.cache_template_extensions = false
end
module Spec
module Rails
module Example
class RailsExampleGroup < Test::Unit::TestCase
# Rails >= r8570 uses setup/teardown_fixtures explicitly
before(:each) do
setup_fixtures if self.respond_to?(:setup_fixtures)
end
after(:each) do
teardown_fixtures if self.respond_to?(:teardown_fixtures)
end
include Spec::Rails::Matchers
include Spec::Rails::Mocks
Spec::Example::ExampleGroupFactory.default(self)
end
end
end
end

View file

@ -0,0 +1,90 @@
require 'spec/mocks/framework'
module Spec
module Rails
module Example
# Provides specialized mock-like behaviour for controller and view examples,
# allowing you to mock or stub calls to render with specific arguments while
# ignoring all other calls.
module RenderObserver
# Similar to mocking +render+ with the exception that calls to +render+ with
# any other options are passed on to the receiver (i.e. controller in
# controller examples, template in view examples).
#
# This is necessary because Rails uses the +render+ method on both
# controllers and templates as a dispatcher to render different kinds of
# things, sometimes resulting in many calls to the render method within one
# request. This approach makes it impossible to use a normal mock object, which
# is designed to observe all incoming messages with a given name.
#
# +expect_render+ is auto-verifying, so failures will be reported without
# requiring you to explicitly request verification.
#
# Also, +expect_render+ uses parts of RSpec's mock expectation framework. Because
# it wraps only a subset of the framework, using this will create no conflict with
# other mock frameworks if you choose to use them. Additionally, the object returned
# by expect_render is an RSpec mock object, which means that you can call any of the
# chained methods available in RSpec's mocks.
#
# == Controller Examples
#
# controller.expect_render(:partial => 'thing', :object => thing)
# controller.expect_render(:partial => 'thing', :collection => things).once
#
# controller.stub_render(:partial => 'thing', :object => thing)
# controller.stub_render(:partial => 'thing', :collection => things).twice
#
# == View Examples
#
# template.expect_render(:partial => 'thing', :object => thing)
# template.expect_render(:partial => 'thing', :collection => things)
#
# template.stub_render(:partial => 'thing', :object => thing)
# template.stub_render(:partial => 'thing', :collection => things)
#
def expect_render(opts={})
register_verify_after_each
expect_render_mock_proxy.should_receive(:render, :expected_from => caller(1)[0]).with(opts)
end
# This is exactly like expect_render, with the exception that the call to render will not
# be verified. Use this if you are trying to isolate your example from a complicated render
# operation but don't care whether it is called or not.
def stub_render(opts={})
register_verify_after_each
expect_render_mock_proxy.stub!(:render, :expected_from => caller(1)[0]).with(opts)
end
def verify_rendered # :nodoc:
expect_render_mock_proxy.rspec_verify
end
def unregister_verify_after_each #:nodoc:
proc = verify_rendered_proc
Spec::Example::ExampleGroup.remove_after(:each, &proc)
end
protected
def verify_rendered_proc #:nodoc:
template = self
@verify_rendered_proc ||= Proc.new do
template.verify_rendered
template.unregister_verify_after_each
end
end
def register_verify_after_each #:nodoc:
proc = verify_rendered_proc
Spec::Example::ExampleGroup.after(:each, &proc)
end
def expect_render_mock_proxy #:nodoc:
@expect_render_mock_proxy ||= Spec::Mocks::Mock.new("expect_render_mock_proxy")
end
end
end
end
end

View file

@ -0,0 +1,178 @@
module Spec
module Rails
module Example
# View Examples live in $RAILS_ROOT/spec/views/.
#
# View Specs use Spec::Rails::Example::ViewExampleGroup,
# which provides access to views without invoking any of your controllers.
# See Spec::Rails::Expectations::Matchers for information about specific
# expectations that you can set on views.
#
# == Example
#
# describe "login/login" do
# before do
# render 'login/login'
# end
#
# it "should display login form" do
# response.should have_tag("form[action=/login]") do
# with_tag("input[type=text][name=email]")
# with_tag("input[type=password][name=password]")
# with_tag("input[type=submit][value=Login]")
# end
# end
# end
class ViewExampleGroup < FunctionalExampleGroup
before(:each) do
ensure_that_flash_and_session_work_properly
end
after(:each) do
ensure_that_base_view_path_is_not_set_across_example_groups
end
def initialize(defined_description, &implementation) #:nodoc:
super
@controller_class_name = "Spec::Rails::Example::ViewExampleGroupController"
end
def ensure_that_flash_and_session_work_properly #:nodoc:
@controller.send :initialize_template_class, @response
@controller.send :assign_shortcuts, @request, @response
@session = @controller.session
@controller.class.send :public, :flash
end
def ensure_that_base_view_path_is_not_set_across_example_groups #:nodoc:
ActionView::Base.base_view_path = nil
end
def set_base_view_path(options) #:nodoc:
ActionView::Base.base_view_path = base_view_path(options)
end
def base_view_path(options) #:nodoc:
"/#{derived_controller_name(options)}/"
end
def derived_controller_name(options) #:nodoc:
parts = subject_of_render(options).split('/').reject { |part| part.empty? }
"#{parts[0..-2].join('/')}"
end
def derived_action_name(options) #:nodoc:
parts = subject_of_render(options).split('/').reject { |part| part.empty? }
"#{parts.last}"
end
def subject_of_render(options) #:nodoc:
[:template, :partial, :file].each do |render_type|
if options.has_key?(render_type)
return options[render_type]
end
end
return ""
end
def add_helpers(options) #:nodoc:
@controller.add_helper("application")
@controller.add_helper(derived_controller_name(options))
@controller.add_helper(options[:helper]) if options[:helper]
options[:helpers].each { |helper| @controller.add_helper(helper) } if options[:helpers]
end
# Renders a template for a View Spec, which then provides access to the result
# through the +response+. Also supports render with :inline, which you can
# use to spec custom form builders, helpers, etc, in the context of a view.
#
# == Examples
#
# render('/people/list')
# render('/people/list', :helper => MyHelper)
# render('/people/list', :helpers => [MyHelper, MyOtherHelper])
# render(:partial => '/people/_address')
# render(:inline => "<% custom_helper 'argument', 'another argument' %>")
#
# See Spec::Rails::Example::ViewExampleGroup for more information.
def render(*args)
options = Hash === args.last ? args.pop : {}
options[:template] = args.first.to_s unless args.empty?
set_base_view_path(options)
add_helpers(options)
assigns[:action_name] = @action_name
@request.path_parameters = {
:controller => derived_controller_name(options),
:action => derived_action_name(options)
}
defaults = { :layout => false }
options = defaults.merge options
@controller.send(:params).reverse_merge! @request.parameters
@controller.send :initialize_current_url
@controller.class.instance_eval %{
def controller_path
"#{derived_controller_name(options)}"
end
def controller_name
"#{derived_controller_name(options).split('/').last}"
end
}
@controller.send :forget_variables_added_to_assigns
@controller.send :render, options
@controller.send :process_cleanup
end
# This provides the template. Use this to set mock
# expectations for dealing with partials
#
# == Example
#
# describe "/person/new" do
# it "should use the form partial" do
# template.should_receive(:render).with(:partial => 'form')
# render "/person/new"
# end
# end
def template
@controller.template
end
Spec::Example::ExampleGroupFactory.register(:view, self)
protected
def _assigns_hash_proxy
@_assigns_hash_proxy ||= AssignsHashProxy.new @controller
end
end
class ViewExampleGroupController < ApplicationController #:nodoc:
include Spec::Rails::Example::RenderObserver
attr_reader :template
def add_helper_for(template_path)
add_helper(template_path.split('/')[0])
end
def add_helper(name)
begin
helper_module = "#{name}_helper".camelize.constantize
rescue
return
end
(class << template; self; end).class_eval do
include helper_module
end
end
end
end
end
end

View file

@ -0,0 +1,12 @@
require 'spec'
require 'spec/rails/extensions/object'
require 'spec/rails/extensions/spec/example/configuration'
require 'spec/rails/extensions/spec/matchers/have'
require 'spec/rails/extensions/active_record/base'
require 'spec/rails/extensions/action_controller/base'
require 'spec/rails/extensions/action_controller/rescue'
require 'spec/rails/extensions/action_controller/test_response'
require 'spec/rails/extensions/action_view/base'

View file

@ -0,0 +1,14 @@
module ActionController
class Base
class << self
def set_view_path(path)
[:append_view_path, :view_paths=, :template_root=].each do |method|
if respond_to?(method)
return send(method, path)
end
end
end
end
end
end

View file

@ -0,0 +1,21 @@
module ActionController
module Rescue
def use_rails_error_handling!
@use_rails_error_handling = true
end
def use_rails_error_handling?
@use_rails_error_handling ||= false
end
protected
def rescue_action_with_fast_errors(exception)
if use_rails_error_handling?
rescue_action_without_fast_errors exception
else
raise exception
end
end
alias_method_chain :rescue_action, :fast_errors
end
end

View file

@ -0,0 +1,11 @@
module ActionController #:nodoc:
class TestResponse #:nodoc:
attr_writer :controller_path
def capture(name)
template.instance_variable_get "@content_for_#{name.to_s}"
end
alias [] capture
end
end

View file

@ -0,0 +1,27 @@
module ActionView #:nodoc:
class Base #:nodoc:
include Spec::Rails::Example::RenderObserver
cattr_accessor :base_view_path
def render_partial(partial_path, local_assigns = nil, deprecated_local_assigns = nil) #:nodoc:
if partial_path.is_a?(String)
unless partial_path.include?("/")
unless self.class.base_view_path.nil?
partial_path = "#{self.class.base_view_path}/#{partial_path}"
end
end
end
super(partial_path, local_assigns, deprecated_local_assigns)
end
alias_method :orig_render, :render
def render(options = {}, old_local_assigns = {}, &block)
if expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_expectation, :render, options)
expect_render_mock_proxy.render(options)
else
unless expect_render_mock_proxy.send(:__mock_proxy).send(:find_matching_method_stub, :render, options)
orig_render(options, old_local_assigns, &block)
end
end
end
end
end

View file

@ -0,0 +1,30 @@
if defined?(ActiveRecord::Base)
module ActiveRecord #:nodoc:
class Base
(class << self; self; end).class_eval do
# Extension for <tt>should have</tt> on AR Model classes
#
# ModelClass.should have(:no).records
# ModelClass.should have(1).record
# ModelClass.should have(n).records
def records
find(:all)
end
alias :record :records
end
# Extension for <tt>should have</tt> on AR Model instances
#
# model.should have(:no).errors_on(:attribute)
# model.should have(1).error_on(:attribute)
# model.should have(n).errors_on(:attribute)
def errors_on(attribute)
self.valid?
[self.errors.on(attribute)].flatten.compact
end
alias :error_on :errors_on
end
end
end

View file

@ -0,0 +1,5 @@
class Object # :nodoc:
def self.path2class(klassname)
klassname.split('::').inject(Object) { |k,n| k.const_get n }
end
end

View file

@ -0,0 +1,71 @@
require 'spec/example/configuration'
begin
module Spec
module Example
class Configuration
# Rails 1.2.3 does a copy of the @inheritable_attributes to the subclass when the subclass is
# created. This causes an ordering issue when setting state on Configuration because the data is
# already copied.
# Iterating over EXAMPLE_GROUP_CLASSES causes the base ExampleGroup classes to have their
# @inheritable_attributes updated.
# TODO: BT - When we no longer support Rails 1.2.3, we can remove this functionality
EXAMPLE_GROUP_CLASSES = [
::Test::Unit::TestCase,
::Spec::Rails::Example::RailsExampleGroup,
::Spec::Rails::Example::FunctionalExampleGroup,
::Spec::Rails::Example::ControllerExampleGroup,
::Spec::Rails::Example::ViewExampleGroup,
::Spec::Rails::Example::HelperExampleGroup,
::Spec::Rails::Example::ModelExampleGroup
]
# All of this is ActiveRecord related and makes no sense if it's not used by the app
if defined?(ActiveRecord::Base)
def initialize
super
self.fixture_path = RAILS_ROOT + '/spec/fixtures'
end
def use_transactional_fixtures
Test::Unit::TestCase.use_transactional_fixtures
end
def use_transactional_fixtures=(value)
EXAMPLE_GROUP_CLASSES.each do |example_group|
example_group.use_transactional_fixtures = value
end
end
def use_instantiated_fixtures
Test::Unit::TestCase.use_instantiated_fixtures
end
def use_instantiated_fixtures=(value)
EXAMPLE_GROUP_CLASSES.each do |example_group|
example_group.use_instantiated_fixtures = value
end
end
def fixture_path
Test::Unit::TestCase.fixture_path
end
def fixture_path=(path)
EXAMPLE_GROUP_CLASSES.each do |example_group|
example_group.fixture_path = path
end
end
def global_fixtures
::Test::Unit::TestCase.fixture_table_names
end
def global_fixtures=(fixtures)
EXAMPLE_GROUP_CLASSES.each do |example_group|
example_group.fixtures(*fixtures)
end
end
end
end
end
end
rescue Exception => e
puts e.message
puts e.backtrace
raise e
end

View file

@ -0,0 +1,21 @@
require 'spec/matchers/have'
module Spec #:nodoc:
module Matchers #:nodoc:
class Have #:nodoc:
alias_method :__original_failure_message, :failure_message
def failure_message
return "expected #{relativities[@relativity]}#{@expected} errors on :#{@args[0]}, got #{@actual}" if @collection_name == :errors_on
return "expected #{relativities[@relativity]}#{@expected} error on :#{@args[0]}, got #{@actual}" if @collection_name == :error_on
return __original_failure_message
end
alias_method :__original_description, :description
def description
return "should have #{relativities[@relativity]}#{@expected} errors on :#{@args[0]}" if @collection_name == :errors_on
return "should have #{relativities[@relativity]}#{@expected} error on :#{@args[0]}" if @collection_name == :error_on
return __original_description
end
end
end
end

View file

@ -0,0 +1,30 @@
dir = File.dirname(__FILE__)
require 'spec/rails/matchers/assert_select'
require 'spec/rails/matchers/have_text'
require 'spec/rails/matchers/include_text'
require 'spec/rails/matchers/redirect_to'
require 'spec/rails/matchers/render_template'
module Spec
module Rails
# Spec::Rails::Expectations::Matchers provides several expectation matchers
# intended to work with Rails components like models and responses. For example:
#
# response.should redirect_to("some/url") #redirect_to(url) is the matcher.
#
# In addition to those you see below, the arbitrary predicate feature of RSpec
# makes the following available as well:
#
# response.should be_success #passes if response.success?
# response.should be_redirect #passes if response.redirect?
#
# Note that many of these matchers are part of a wrapper of <tt>assert_select</tt>, so
# the documentation comes straight from that with some slight modifications.
# <tt>assert_select</tt> is a Test::Unit extension originally contributed to the
# Rails community as a plugin by Assaf Arkin and eventually shipped as part of Rails.
#
# For more info on <tt>assert_select</tt>, see the relevant Rails documentation.
module Matchers
end
end
end

View file

@ -0,0 +1,130 @@
# This is a wrapper of assert_select for rspec.
module Spec # :nodoc:
module Rails
module Matchers
class AssertSelect #:nodoc:
def initialize(assertion, spec_scope, *args, &block)
@assertion = assertion
@spec_scope = spec_scope
@args = args
@block = block
end
def matches?(response_or_text, &block)
if ActionController::TestResponse === response_or_text and
response_or_text.headers.key?('Content-Type') and
response_or_text.headers['Content-Type'].to_sym == :xml
@args.unshift(HTML::Document.new(response_or_text.body, false, true).root)
elsif String === response_or_text
@args.unshift(HTML::Document.new(response_or_text).root)
end
@block = block if block
begin
@spec_scope.send(@assertion, *@args, &@block)
rescue ::Test::Unit::AssertionFailedError => @error
end
@error.nil?
end
def failure_message; @error.message; end
def negative_failure_message; "should not #{description}, but did"; end
def description
{
:assert_select => "have tag#{format_args(*@args)}",
:assert_select_email => "send email#{format_args(*@args)}",
}[@assertion]
end
private
def format_args(*args)
return "" if args.empty?
return "(#{arg_list(*args)})"
end
def arg_list(*args)
args.collect do |arg|
arg.respond_to?(:description) ? arg.description : arg.inspect
end.join(", ")
end
end
# :call-seq:
# response.should have_tag(*args, &block)
# string.should have_tag(*args, &block)
#
# wrapper for assert_select with additional support for using
# css selectors to set expectation on Strings. Use this in
# helper specs, for example, to set expectations on the results
# of helper methods.
#
# == Examples
#
# # in a controller spec
# response.should have_tag("div", "some text")
#
# # in a helper spec (person_address_tag is a method in the helper)
# person_address_tag.should have_tag("input#person_address")
#
# see documentation for assert_select at http://api.rubyonrails.org/
def have_tag(*args, &block)
AssertSelect.new(:assert_select, self, *args, &block)
end
# wrapper for a nested assert_select
#
# response.should have_tag("div#form") do
# with_tag("input#person_name[name=?]", "person[name]")
# end
#
# see documentation for assert_select at http://api.rubyonrails.org/
def with_tag(*args, &block)
should have_tag(*args, &block)
end
# wrapper for a nested assert_select with false
#
# response.should have_tag("div#1") do
# without_tag("span", "some text that shouldn't be there")
# end
#
# see documentation for assert_select at http://api.rubyonrails.org/
def without_tag(*args, &block)
should_not have_tag(*args, &block)
end
# :call-seq:
# response.should have_rjs(*args, &block)
#
# wrapper for assert_select_rjs
#
# see documentation for assert_select_rjs at http://api.rubyonrails.org/
def have_rjs(*args, &block)
AssertSelect.new(:assert_select_rjs, self, *args, &block)
end
# :call-seq:
# response.should send_email(*args, &block)
#
# wrapper for assert_select_email
#
# see documentation for assert_select_email at http://api.rubyonrails.org/
def send_email(*args, &block)
AssertSelect.new(:assert_select_email, self, *args, &block)
end
# wrapper for assert_select_encoded
#
# see documentation for assert_select_encoded at http://api.rubyonrails.org/
def with_encoded(*args, &block)
should AssertSelect.new(:assert_select_encoded, self, *args, &block)
end
end
end
end

View file

@ -0,0 +1,57 @@
module Spec
module Rails
module Matchers
class HaveText #:nodoc:
def initialize(expected)
@expected = expected
end
def matches?(response_or_text)
@actual = response_or_text.respond_to?(:body) ? response_or_text.body : response_or_text
return actual =~ expected if Regexp === expected
return actual == expected unless Regexp === expected
end
def failure_message
"expected #{expected.inspect}, got #{actual.inspect}"
end
def negative_failure_message
"expected not to have text #{expected.inspect}"
end
def to_s
"have text #{expected.inspect}"
end
private
attr_reader :expected
attr_reader :actual
end
# :call-seq:
# response.should have_text(expected)
# response.should_not have_text(expected)
#
# Accepts a String or a Regexp, matching a String using ==
# and a Regexp using =~.
#
# If response_or_text has a #body, then that is used as to match against
# else it uses response_or_text
#
# Use this instead of <tt>response.should have_tag()</tt>
# when you want to match the whole string or whole body
#
# == Examples
#
# response.should have_text("This is the expected text")
def have_text(text)
HaveText.new(text)
end
end
end
end

View file

@ -0,0 +1,54 @@
module Spec
module Rails
module Matchers
class IncludeText #:nodoc:
def initialize(expected)
@expected = expected
end
def matches?(response_or_text)
@actual = response_or_text.respond_to?(:body) ? response_or_text.body : response_or_text
return actual.include?(expected)
end
def failure_message
"expected to find #{expected.inspect} in #{actual.inspect}"
end
def negative_failure_message
"expected not to include text #{expected.inspect}"
end
def to_s
"include text #{expected.inspect}"
end
private
attr_reader :expected
attr_reader :actual
end
# :call-seq:
# response.should include_text(expected)
# response.should_not include_text(expected)
#
# Accepts a String, matching using include?
#
# Use this instead of <tt>response.should have_text()</tt>
# when you either don't know or don't care where on the page
# this text appears.
#
# == Examples
#
# response.should include_text("This text will be in the actual string")
def include_text(text)
IncludeText.new(text)
end
end
end
end

View file

@ -0,0 +1,113 @@
module Spec
module Rails
module Matchers
class RedirectTo #:nodoc:
def initialize(request, expected)
@expected = expected
@request = request
end
def matches?(response)
@redirected = response.redirect?
@actual = response.redirect_url
return false unless @redirected
if @expected.instance_of? Hash
return false unless @actual =~ %r{^\w+://#{@request.host}}
return false unless actual_redirect_to_valid_route
return actual_hash == expected_hash
else
return @actual == expected_url
end
end
def actual_hash
hash_from_url @actual
end
def expected_hash
hash_from_url expected_url
end
def actual_redirect_to_valid_route
actual_hash
end
def hash_from_url(url)
query_hash(url).merge(path_hash(url)).with_indifferent_access
end
def path_hash(url)
path = url.sub(%r{^\w+://#{@request.host}(?::\d+)?}, "").split("?", 2)[0]
ActionController::Routing::Routes.recognize_path path
end
def query_hash(url)
query = url.split("?", 2)[1] || ""
QueryParameterParser.parse_query_parameters(query, @request)
end
def expected_url
case @expected
when Hash
return ActionController::UrlRewriter.new(@request, {}).rewrite(@expected)
when :back
return @request.env['HTTP_REFERER']
when %r{^\w+://.*}
return @expected
else
return "http://#{@request.host}" + (@expected.split('')[0] == '/' ? '' : '/') + @expected
end
end
def failure_message
if @redirected
return %Q{expected redirect to #{@expected.inspect}, got redirect to #{@actual.inspect}}
else
return %Q{expected redirect to #{@expected.inspect}, got no redirect}
end
end
def negative_failure_message
return %Q{expected not to be redirected to #{@expected.inspect}, but was} if @redirected
end
def description
"redirect to #{@actual.inspect}"
end
class QueryParameterParser
def self.parse_query_parameters(query, request)
if defined?(CGIMethods)
CGIMethods.parse_query_parameters(query)
else
request.class.parse_query_parameters(query)
end
end
end
end
# :call-seq:
# response.should redirect_to(url)
# response.should redirect_to(:action => action_name)
# response.should redirect_to(:controller => controller_name, :action => action_name)
# response.should_not redirect_to(url)
# response.should_not redirect_to(:action => action_name)
# response.should_not redirect_to(:controller => controller_name, :action => action_name)
#
# Passes if the response is a redirect to the url, action or controller/action.
# Useful in controller specs (integration or isolation mode).
#
# == Examples
#
# response.should redirect_to("path/to/action")
# response.should redirect_to("http://test.host/path/to/action")
# response.should redirect_to(:action => 'list')
def redirect_to(opts)
RedirectTo.new(request, opts)
end
end
end
end

View file

@ -0,0 +1,70 @@
module Spec
module Rails
module Matchers
class RenderTemplate #:nodoc:
def initialize(expected, controller)
@controller = controller
@expected = expected
end
def matches?(response)
@actual = response.rendered_file
full_path(@actual) == full_path(@expected)
end
def failure_message
"expected #{@expected.inspect}, got #{@actual.inspect}"
end
def negative_failure_message
"expected not to render #{@expected.inspect}, but did"
end
def description
"render template #{@expected.inspect}"
end
private
def full_path(path)
return nil if path.nil?
path.include?('/') ? path : "#{@controller.class.to_s.underscore.gsub('_controller','')}/#{path}"
end
end
# :call-seq:
# response.should render_template(path)
# response.should_not render_template(path)
#
# Passes if the specified template is rendered by the response.
# Useful in controller specs (integration or isolation mode).
#
# <code>path</code> can include the controller path or not. It
# can also include an optional extension (no extension assumes .rhtml).
#
# Note that partials must be spelled with the preceding underscore.
#
# == Examples
#
# response.should render_template('list')
# response.should render_template('same_controller/list')
# response.should render_template('other_controller/list')
#
# #rjs
# response.should render_template('list.rjs')
# response.should render_template('same_controller/list.rjs')
# response.should render_template('other_controller/list.rjs')
#
# #partials
# response.should render_template('_a_partial')
# response.should render_template('same_controller/_a_partial')
# response.should render_template('other_controller/_a_partial')
def render_template(path)
RenderTemplate.new(path.to_s, @controller)
end
end
end
end

View file

@ -0,0 +1,117 @@
module Spec
module Rails
class IllegalDataAccessException < StandardError; end
module Mocks
# Creates a mock object instance for a +model_class+ with common
# methods stubbed out. Additional methods may be easily stubbed (via
# add_stubs) if +stubs+ is passed.
def mock_model(model_class, options_and_stubs = {})
id = next_id
options_and_stubs.reverse_merge!({
:id => id,
:to_param => id.to_s,
:new_record? => false,
:errors => stub("errors", :count => 0)
})
m = mock("#{model_class.name}_#{options_and_stubs[:id]}", options_and_stubs)
m.send(:__mock_proxy).instance_eval <<-CODE
def @target.is_a?(other)
#{model_class}.ancestors.include?(other)
end
def @target.kind_of?(other)
#{model_class}.ancestors.include?(other)
end
def @target.instance_of?(other)
other == #{model_class}
end
def @target.class
#{model_class}
end
CODE
yield m if block_given?
m
end
module ModelStubber
def connection
raise Spec::Rails::IllegalDataAccessException.new("stubbed models are not allowed to access the database")
end
def new_record?
id.nil?
end
def as_new_record
self.id = nil
self
end
end
# :call-seq:
# stub_model(Model)
# stub_model(Model).as_new_record
# stub_model(Model, hash_of_stubs)
#
# Creates an instance of +Model+ that is prohibited from accessing the
# database. For each key in +hash_of_stubs+, if the model has a
# matching attribute (determined by asking it, which it answers based
# on schema.rb) are simply assigned the submitted values. If the model
# does not have a matching attribute, the key/value pair is assigned
# as a stub return value using RSpec's mocking/stubbing framework.
#
# new_record? is overridden to return the result of id.nil? This means
# that by default new_record? will return false. If you want the
# object to behave as a new record, sending it +as_new_record+ will
# set the id to nil. You can also explicitly set :id => nil, in which
# case new_record? will return true, but using +as_new_record+ makes
# the example a bit more descriptive.
#
# While you can use stub_model in any example (model, view,
# controller, helper), it is especially useful in view examples,
# which are inherently more state-based than interaction-based.
#
# == Examples
#
# stub_model(Person)
# stub_model(Person).as_new_record
# stub_model(Person, :id => 37)
# stub_model(Person) do |person|
# model.first_name = "David"
# end
def stub_model(model_class, stubs = {})
stubs = {:id => next_id}.merge(stubs)
returning model_class.new do |model|
model.id = stubs.delete(:id)
model.extend ModelStubber
stubs.each do |k,v|
if model.has_attribute?(k)
model[k] = stubs.delete(k)
end
end
add_stubs(model, stubs)
yield model if block_given?
end
end
#--
# TODO - Shouldn't this just be an extension of stub! ??
# - object.stub!(:method => return_value, :method2 => return_value2, :etc => etc)
#++
# Stubs methods on +object+ (if +object+ is a symbol or string a new mock
# with that name will be created). +stubs+ is a Hash of <tt>method=>value</tt>
def add_stubs(object, stubs = {}) #:nodoc:
m = [String, Symbol].index(object.class) ? mock(object.to_s) : object
stubs.each {|k,v| m.stub!(k).and_return(v)}
m
end
private
@@model_id = 1000
def next_id
@@model_id += 1
end
end
end
end

View file

@ -0,0 +1,71 @@
# WARNING - THIS IS PURELY EXPERIMENTAL AT THIS POINT
# Courtesy of Brian Takita and Yurii Rashkovskii
$:.unshift File.join(File.dirname(__FILE__), *%w[.. .. .. .. rspec lib])
if defined?(ActiveRecord::Base)
require 'test_help'
else
require 'action_controller/test_process'
require 'action_controller/integration'
end
require 'test/unit/testresult'
require 'spec'
require 'spec/rails'
Test::Unit.run = true
ActionController::Integration::Session.send(:include, Spec::Matchers)
ActionController::Integration::Session.send(:include, Spec::Rails::Matchers)
class RailsStory < ActionController::IntegrationTest
if defined?(ActiveRecord::Base)
self.use_transactional_fixtures = true
else
def self.fixture_table_names; []; end # Workaround for projects that don't use ActiveRecord
end
def initialize #:nodoc:
# TODO - eliminate this hack, which is here to stop
# Rails Stories from dumping the example summary.
Spec::Runner::Options.class_eval do
def examples_should_be_run?
false
end
end
@_result = Test::Unit::TestResult.new
end
end
class ActiveRecordSafetyListener
include Singleton
def scenario_started(*args)
if defined?(ActiveRecord::Base)
ActiveRecord::Base.send :increment_open_transactions unless Rails::VERSION::STRING == "1.1.6"
ActiveRecord::Base.connection.begin_db_transaction
end
end
def scenario_succeeded(*args)
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.rollback_db_transaction
ActiveRecord::Base.send :decrement_open_transactions unless Rails::VERSION::STRING == "1.1.6"
end
end
alias :scenario_pending :scenario_succeeded
alias :scenario_failed :scenario_succeeded
end
class Spec::Story::Runner::ScenarioRunner
def initialize
@listeners = [ActiveRecordSafetyListener.instance]
end
end
class Spec::Story::GivenScenario
def perform(instance, name = nil)
scenario = Spec::Story::Runner::StoryRunner.scenario_from_current_story @name
runner = Spec::Story::Runner::ScenarioRunner.new
runner.instance_variable_set(:@listeners,[])
runner.run(scenario, instance)
end
end

View file

@ -0,0 +1,23 @@
module Spec
module Rails
module VERSION #:nodoc:
BUILD_TIME_UTC = 20080615141040
end
end
end
# Verify that the plugin has the same revision as RSpec
if Spec::Rails::VERSION::BUILD_TIME_UTC != Spec::VERSION::BUILD_TIME_UTC
raise <<-EOF
############################################################################
Your RSpec on Rails plugin is incompatible with your installed RSpec.
RSpec : #{Spec::VERSION::BUILD_TIME_UTC}
RSpec on Rails : #{Spec::Rails::VERSION::BUILD_TIME_UTC}
Make sure your RSpec on Rails plugin is compatible with your RSpec gem.
See http://rspec.rubyforge.org/documentation/rails/install.html for details.
############################################################################
EOF
end