diff --git a/README.rdoc b/README.rdoc
index 13f7a008..80f8171d 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -10,7 +10,7 @@ Assertions:: Many common rails testing idioms have been distilled into a set of
= Usage
-=== Context Helpers (ThoughtBot::Shoulda::Context)
+=== Context Helpers (Shoulda::Context)
Stop killing your fingers with all of those underscores... Name your tests with plain sentences!
@@ -43,7 +43,7 @@ Produces the following test methods:
So readable!
-=== ActiveRecord Tests (ThoughtBot::Shoulda::ActiveRecord)
+=== ActiveRecord Tests (Shoulda::ActiveRecord)
Quick macro tests for your ActiveRecord associations and validations:
@@ -73,7 +73,7 @@ Quick macro tests for your ActiveRecord associations and validations:
Makes TDD so much easier.
-=== Controller Tests (ThoughtBot::Shoulda::Controller::ClassMethods)
+=== Controller Tests (Shoulda::Controller::ClassMethods)
Macros to test the most common controller patterns...
@@ -105,7 +105,7 @@ Test entire controllers in a few lines...
should_be_restful generates 40 tests on the fly, for both html and xml requests.
-=== Helpful Assertions (ThoughtBot::Shoulda::General)
+=== Helpful Assertions (Shoulda::General)
More to come here, but have fun with what's there.
diff --git a/lib/shoulda.rb b/lib/shoulda.rb
index a90ff183..cd571df2 100644
--- a/lib/shoulda.rb
+++ b/lib/shoulda.rb
@@ -26,10 +26,10 @@ module Test # :nodoc: all
module Unit
class TestCase
- include ThoughtBot::Shoulda::General
- include ThoughtBot::Shoulda::Controller
+ include Shoulda::General
+ include Shoulda::Controller
- extend ThoughtBot::Shoulda::ActiveRecord
+ extend Shoulda::ActiveRecord
end
end
end
@@ -37,7 +37,7 @@ end
module ActionController #:nodoc: all
module Integration
class Session
- include ThoughtBot::Shoulda::General
+ include Shoulda::General
end
end
end
diff --git a/lib/shoulda/active_record_helpers.rb b/lib/shoulda/active_record_helpers.rb
index ec840571..15639864 100644
--- a/lib/shoulda/active_record_helpers.rb
+++ b/lib/shoulda/active_record_helpers.rb
@@ -1,604 +1,602 @@
-module ThoughtBot # :nodoc:
- module Shoulda # :nodoc:
- # = Macro test helpers for your active record models
+module Shoulda # :nodoc:
+ # = Macro test helpers for your active record models
+ #
+ # These helpers will test most of the validations and associations for your ActiveRecord models.
+ #
+ # class UserTest < Test::Unit::TestCase
+ # should_require_attributes :name, :phone_number
+ # should_not_allow_values_for :phone_number, "abcd", "1234"
+ # should_allow_values_for :phone_number, "(123) 456-7890"
+ #
+ # should_protect_attributes :password
+ #
+ # should_have_one :profile
+ # should_have_many :dogs
+ # should_have_many :messes, :through => :dogs
+ # should_belong_to :lover
+ # end
+ #
+ # For all of these helpers, the last parameter may be a hash of options.
+ #
+ module ActiveRecord
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
#
- # These helpers will test most of the validations and associations for your ActiveRecord models.
+ # Options:
+ # * :message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /blank/
#
- # class UserTest < Test::Unit::TestCase
- # should_require_attributes :name, :phone_number
- # should_not_allow_values_for :phone_number, "abcd", "1234"
- # should_allow_values_for :phone_number, "(123) 456-7890"
- #
- # should_protect_attributes :password
- #
- # should_have_one :profile
- # should_have_many :dogs
- # should_have_many :messes, :through => :dogs
- # should_belong_to :lover
- # end
+ # Example:
+ # should_require_attributes :name, :phone_number
#
- # For all of these helpers, the last parameter may be a hash of options.
- #
- module ActiveRecord
- # Ensures that the model cannot be saved if one of the attributes listed is not present.
- #
- # Options:
- # * :message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /blank/
- #
- # Example:
- # should_require_attributes :name, :phone_number
- #
- def should_require_attributes(*attributes)
- message = get_options!(attributes, :message)
- message ||= /blank/
- klass = model_class
-
- attributes.each do |attribute|
- should "require #{attribute} to be set" do
- object = klass.new
- object.send("#{attribute}=", nil)
- assert !object.valid?, "#{klass.name} does not require #{attribute}."
- assert object.errors.on(attribute), "#{klass.name} does not require #{attribute}."
- assert_contains(object.errors.on(attribute), message)
- end
+ def should_require_attributes(*attributes)
+ message = get_options!(attributes, :message)
+ message ||= /blank/
+ klass = model_class
+
+ attributes.each do |attribute|
+ should "require #{attribute} to be set" do
+ object = klass.new
+ object.send("#{attribute}=", nil)
+ assert !object.valid?, "#{klass.name} does not require #{attribute}."
+ assert object.errors.on(attribute), "#{klass.name} does not require #{attribute}."
+ assert_contains(object.errors.on(attribute), message)
end
end
+ end
- # Ensures that the model cannot be saved if one of the attributes listed is not unique.
- # Requires an existing record
- #
- # Options:
- # * :message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /taken/
- # * :scoped_to - field(s) to scope the uniqueness to.
- #
- # Examples:
- # should_require_unique_attributes :keyword, :username
- # should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
- # should_require_unique_attributes :email, :scoped_to => :name
- # should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
- #
- def should_require_unique_attributes(*attributes)
- message, scope = get_options!(attributes, :message, :scoped_to)
- scope = [*scope].compact
- message ||= /taken/
-
- klass = model_class
- attributes.each do |attribute|
- attribute = attribute.to_sym
- should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" if scope}" do
- assert existing = klass.find(:first), "Can't find first #{klass}"
- object = klass.new
-
- object.send(:"#{attribute}=", existing.send(attribute))
- if !scope.blank?
- scope.each do |s|
- assert_respond_to object, :"#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute."
- object.send(:"#{s}=", existing.send(s))
- end
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
+ # Requires an existing record
+ #
+ # Options:
+ # * :message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /taken/
+ # * :scoped_to - field(s) to scope the uniqueness to.
+ #
+ # Examples:
+ # should_require_unique_attributes :keyword, :username
+ # should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
+ # should_require_unique_attributes :email, :scoped_to => :name
+ # should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
+ #
+ def should_require_unique_attributes(*attributes)
+ message, scope = get_options!(attributes, :message, :scoped_to)
+ scope = [*scope].compact
+ message ||= /taken/
+
+ klass = model_class
+ attributes.each do |attribute|
+ attribute = attribute.to_sym
+ should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" if scope}" do
+ assert existing = klass.find(:first), "Can't find first #{klass}"
+ object = klass.new
+
+ object.send(:"#{attribute}=", existing.send(attribute))
+ if !scope.blank?
+ scope.each do |s|
+ assert_respond_to object, :"#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute."
+ object.send(:"#{s}=", existing.send(s))
+ end
+ end
+
+ assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}."
+ assert object.errors.on(attribute), "#{klass.name} does not require a unique value for #{attribute}."
+
+ assert_contains(object.errors.on(attribute), message)
+
+ # Now test that the object is valid when changing the scoped attribute
+ # TODO: There is a chance that we could change the scoped field
+ # to a value that's already taken. An alternative implementation
+ # could actually find all values for scope and create a unique
+ # one.
+ if !scope.blank?
+ scope.each do |s|
+ # Assume the scope is a foreign key if the field is nil
+ object.send(:"#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next)
end
-
- assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}."
- assert object.errors.on(attribute), "#{klass.name} does not require a unique value for #{attribute}."
-
- assert_contains(object.errors.on(attribute), message)
-
- # Now test that the object is valid when changing the scoped attribute
- # TODO: There is a chance that we could change the scoped field
- # to a value that's already taken. An alternative implementation
- # could actually find all values for scope and create a unique
- # one.
- if !scope.blank?
- scope.each do |s|
- # Assume the scope is a foreign key if the field is nil
- object.send(:"#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next)
- end
- object.errors.clear
- object.valid?
- scope.each do |s|
- assert_does_not_contain(object.errors.on(attribute), message,
- "after :#{s} set to #{object.send(s.to_sym)}")
- end
+ object.errors.clear
+ object.valid?
+ scope.each do |s|
+ assert_does_not_contain(object.errors.on(attribute), message,
+ "after :#{s} set to #{object.send(s.to_sym)}")
end
end
end
end
+ end
- # Ensures that the attribute cannot be set on mass update.
- # Requires an existing record.
- #
- # should_protect_attributes :password, :admin_flag
- #
- def should_protect_attributes(*attributes)
- get_options!(attributes)
- klass = model_class
+ # Ensures that the attribute cannot be set on mass update.
+ # Requires an existing record.
+ #
+ # should_protect_attributes :password, :admin_flag
+ #
+ def should_protect_attributes(*attributes)
+ get_options!(attributes)
+ klass = model_class
- attributes.each do |attribute|
- attribute = attribute.to_sym
- should "protect #{attribute} from mass updates" do
- protected = klass.protected_attributes || []
- accessible = klass.accessible_attributes || []
+ attributes.each do |attribute|
+ attribute = attribute.to_sym
+ should "protect #{attribute} from mass updates" do
+ protected = klass.protected_attributes || []
+ accessible = klass.accessible_attributes || []
- assert protected.include?(attribute.to_s) || !accessible.include?(attribute.to_s),
- (accessible.empty? ?
- "#{klass} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." :
- "#{klass} has made #{attribute} accessible")
- end
+ assert protected.include?(attribute.to_s) || !accessible.include?(attribute.to_s),
+ (accessible.empty? ?
+ "#{klass} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." :
+ "#{klass} has made #{attribute} accessible")
+ end
+ end
+ end
+
+ # Ensures that the attribute cannot be set to the given values
+ # Requires an existing record
+ #
+ # Options:
+ # * :message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /invalid/
+ #
+ # Example:
+ # should_not_allow_values_for :isbn, "bad 1", "bad 2"
+ #
+ def should_not_allow_values_for(attribute, *bad_values)
+ message = get_options!(bad_values, :message)
+ message ||= /invalid/
+ klass = model_class
+ bad_values.each do |v|
+ should "not allow #{attribute} to be set to #{v.inspect}" do
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", v)
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
+ assert_contains(object.errors.on(attribute), message, "when set to \"#{v}\"")
+ end
+ end
+ end
+
+ # Ensures that the attribute can be set to the given values.
+ # Requires an existing record
+ #
+ # Example:
+ # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
+ #
+ def should_allow_values_for(attribute, *good_values)
+ get_options!(good_values)
+ klass = model_class
+ good_values.each do |v|
+ should "allow #{attribute} to be set to #{v.inspect}" do
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", v)
+ object.save
+ assert_nil object.errors.on(attribute)
+ end
+ end
+ end
+
+ # Ensures that the length of the attribute is in the given range
+ # Requires an existing record
+ #
+ # Options:
+ # * :short_message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /short/
+ # * :long_message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /long/
+ #
+ # Example:
+ # should_ensure_length_in_range :password, (6..20)
+ #
+ def should_ensure_length_in_range(attribute, range, opts = {})
+ short_message, long_message = get_options!([opts], :short_message, :long_message)
+ short_message ||= /short/
+ long_message ||= /long/
+
+ klass = model_class
+ min_length = range.first
+ max_length = range.last
+ same_length = (min_length == max_length)
+
+ if min_length > 0
+ should "not allow #{attribute} to be less than #{min_length} chars long" do
+ min_value = "x" * (min_length - 1)
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", min_value)
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
+ assert object.errors.on(attribute),
+ "There are no errors set on #{attribute} after being set to \"#{min_value}\""
+ assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
+ end
+ end
+
+ if min_length > 0
+ should "allow #{attribute} to be exactly #{min_length} chars long" do
+ min_value = "x" * min_length
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", min_value)
+ object.save
+ assert_does_not_contain(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
end
end
- # Ensures that the attribute cannot be set to the given values
- # Requires an existing record
- #
- # Options:
- # * :message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /invalid/
- #
- # Example:
- # should_not_allow_values_for :isbn, "bad 1", "bad 2"
- #
- def should_not_allow_values_for(attribute, *bad_values)
- message = get_options!(bad_values, :message)
- message ||= /invalid/
- klass = model_class
- bad_values.each do |v|
- should "not allow #{attribute} to be set to #{v.inspect}" do
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", v)
- assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
- assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
- assert_contains(object.errors.on(attribute), message, "when set to \"#{v}\"")
- end
- end
- end
-
- # Ensures that the attribute can be set to the given values.
- # Requires an existing record
- #
- # Example:
- # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
- #
- def should_allow_values_for(attribute, *good_values)
- get_options!(good_values)
- klass = model_class
- good_values.each do |v|
- should "allow #{attribute} to be set to #{v.inspect}" do
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", v)
- object.save
- assert_nil object.errors.on(attribute)
- end
- end
+ should "not allow #{attribute} to be more than #{max_length} chars long" do
+ max_value = "x" * (max_length + 1)
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", max_value)
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\""
+ assert object.errors.on(attribute),
+ "There are no errors set on #{attribute} after being set to \"#{max_value}\""
+ assert_contains(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"")
end
- # Ensures that the length of the attribute is in the given range
- # Requires an existing record
- #
- # Options:
- # * :short_message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /short/
- # * :long_message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /long/
- #
- # Example:
- # should_ensure_length_in_range :password, (6..20)
- #
- def should_ensure_length_in_range(attribute, range, opts = {})
- short_message, long_message = get_options!([opts], :short_message, :long_message)
- short_message ||= /short/
- long_message ||= /long/
-
- klass = model_class
- min_length = range.first
- max_length = range.last
- same_length = (min_length == max_length)
-
- if min_length > 0
- should "not allow #{attribute} to be less than #{min_length} chars long" do
- min_value = "x" * (min_length - 1)
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", min_value)
- assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
- assert object.errors.on(attribute),
- "There are no errors set on #{attribute} after being set to \"#{min_value}\""
- assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
- end
- end
-
- if min_length > 0
- should "allow #{attribute} to be exactly #{min_length} chars long" do
- min_value = "x" * min_length
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", min_value)
- object.save
- assert_does_not_contain(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
- end
- end
-
- should "not allow #{attribute} to be more than #{max_length} chars long" do
- max_value = "x" * (max_length + 1)
+ unless same_length
+ should "allow #{attribute} to be exactly #{max_length} chars long" do
+ max_value = "x" * max_length
assert object = klass.find(:first), "Can't find first #{klass}"
object.send("#{attribute}=", max_value)
- assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\""
- assert object.errors.on(attribute),
- "There are no errors set on #{attribute} after being set to \"#{max_value}\""
- assert_contains(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"")
- end
-
- unless same_length
- should "allow #{attribute} to be exactly #{max_length} chars long" do
- max_value = "x" * max_length
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", max_value)
- object.save
- assert_does_not_contain(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"")
- end
- end
- end
-
- # Ensures that the length of the attribute is at least a certain length
- # Requires an existing record
- #
- # Options:
- # * :short_message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /short/
- #
- # Example:
- # should_ensure_length_at_least :name, 3
- #
- def should_ensure_length_at_least(attribute, min_length, opts = {})
- short_message = get_options!([opts], :short_message)
- short_message ||= /short/
-
- klass = model_class
-
- if min_length > 0
- min_value = "x" * (min_length - 1)
- should "not allow #{attribute} to be less than #{min_length} chars long" do
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", min_value)
- assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
- assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\""
- assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
- end
- end
- should "allow #{attribute} to be at least #{min_length} chars long" do
- valid_value = "x" * (min_length)
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", valid_value)
- assert object.save, "Could not save #{klass} with #{attribute} set to \"#{valid_value}\""
- end
- end
-
- # Ensure that the attribute is in the range specified
- # Requires an existing record
- #
- # Options:
- # * :low_message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /included/
- # * :high_message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /included/
- #
- # Example:
- # should_ensure_value_in_range :age, (0..100)
- #
- def should_ensure_value_in_range(attribute, range, opts = {})
- low_message, high_message = get_options!([opts], :low_message, :high_message)
- low_message ||= /included/
- high_message ||= /included/
-
- klass = model_class
- min = range.first
- max = range.last
-
- should "not allow #{attribute} to be less than #{min}" do
- v = min - 1
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", v)
- assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
- assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
- assert_contains(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
- end
-
- should "allow #{attribute} to be #{min}" do
- v = min
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", v)
object.save
- assert_does_not_contain(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
+ assert_does_not_contain(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"")
end
-
- should "not allow #{attribute} to be more than #{max}" do
- v = max + 1
+ end
+ end
+
+ # Ensures that the length of the attribute is at least a certain length
+ # Requires an existing record
+ #
+ # Options:
+ # * :short_message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /short/
+ #
+ # Example:
+ # should_ensure_length_at_least :name, 3
+ #
+ def should_ensure_length_at_least(attribute, min_length, opts = {})
+ short_message = get_options!([opts], :short_message)
+ short_message ||= /short/
+
+ klass = model_class
+
+ if min_length > 0
+ min_value = "x" * (min_length - 1)
+ should "not allow #{attribute} to be less than #{min_length} chars long" do
assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", v)
- assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
- assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
- assert_contains(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
+ object.send("#{attribute}=", min_value)
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\""
+ assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
end
+ end
+ should "allow #{attribute} to be at least #{min_length} chars long" do
+ valid_value = "x" * (min_length)
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", valid_value)
+ assert object.save, "Could not save #{klass} with #{attribute} set to \"#{valid_value}\""
+ end
+ end
- should "allow #{attribute} to be #{max}" do
- v = max
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", v)
- object.save
- assert_does_not_contain(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
- end
- end
+ # Ensure that the attribute is in the range specified
+ # Requires an existing record
+ #
+ # Options:
+ # * :low_message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /included/
+ # * :high_message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /included/
+ #
+ # Example:
+ # should_ensure_value_in_range :age, (0..100)
+ #
+ def should_ensure_value_in_range(attribute, range, opts = {})
+ low_message, high_message = get_options!([opts], :low_message, :high_message)
+ low_message ||= /included/
+ high_message ||= /included/
- # Ensure that the attribute is numeric
- # Requires an existing record
- #
- # Options:
- # * :message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /number/
- #
- # Example:
- # should_only_allow_numeric_values_for :age
- #
- def should_only_allow_numeric_values_for(*attributes)
- message = get_options!(attributes, :message)
- message ||= /number/
- klass = model_class
- attributes.each do |attribute|
- attribute = attribute.to_sym
- should "only allow numeric values for #{attribute}" do
- assert object = klass.find(:first), "Can't find first #{klass}"
- object.send(:"#{attribute}=", "abcd")
- assert !object.valid?, "Instance is still valid"
- assert_contains(object.errors.on(attribute), message)
- end
- end
+ klass = model_class
+ min = range.first
+ max = range.last
+
+ should "not allow #{attribute} to be less than #{min}" do
+ v = min - 1
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", v)
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
+ assert_contains(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
end
- # Ensures that the has_many relationship exists. Will also test that the
- # associated table has the required columns. Works with polymorphic
- # associations.
- #
- # Options:
- # * :through - association name for has_many :through
- # * :dependent - tests that the association makes use of the dependent option.
- #
- # Example:
- # should_have_many :friends
- # should_have_many :enemies, :through => :friends
- # should_have_many :enemies, :dependent => :destroy
- #
- def should_have_many(*associations)
- through, dependent = get_options!(associations, :through, :dependent)
- klass = model_class
- associations.each do |association|
- name = "have many #{association}"
- name += " through #{through}" if through
- name += " dependent => #{dependent}" if dependent
- should name do
- reflection = klass.reflect_on_association(association)
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
- assert_equal :has_many, reflection.macro
-
- associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
-
- if through
- through_reflection = klass.reflect_on_association(through)
- assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
- assert_equal(through, reflection.options[:through])
- end
-
- if dependent
- assert_equal dependent.to_s,
- reflection.options[:dependent].to_s,
- "#{associated_klass.name} should have #{dependent} dependency"
- end
-
- # Check for the existence of the foreign key on the other table
- unless reflection.options[:through]
- if reflection.options[:foreign_key]
- fk = reflection.options[:foreign_key]
- elsif reflection.options[:as]
- fk = reflection.options[:as].to_s.foreign_key
- else
- fk = reflection.primary_key_name
- end
-
- assert associated_klass.column_names.include?(fk.to_s),
- "#{associated_klass.name} does not have a #{fk} foreign key."
- end
- end
- end
+ should "allow #{attribute} to be #{min}" do
+ v = min
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", v)
+ object.save
+ assert_does_not_contain(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
end
- # Ensure that the has_one relationship exists. Will also test that the
- # associated table has the required columns. Works with polymorphic
- # associations.
- #
- # Example:
- # should_have_one :god # unless hindu
- #
- def should_have_one(*associations)
- get_options!(associations)
- klass = model_class
- associations.each do |association|
- should "have one #{association}" do
- reflection = klass.reflect_on_association(association)
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
- assert_equal :has_one, reflection.macro
-
- associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
+ should "not allow #{attribute} to be more than #{max}" do
+ v = max + 1
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", v)
+ assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
+ assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
+ assert_contains(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
+ end
+ should "allow #{attribute} to be #{max}" do
+ v = max
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send("#{attribute}=", v)
+ object.save
+ assert_does_not_contain(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
+ end
+ end
+
+ # Ensure that the attribute is numeric
+ # Requires an existing record
+ #
+ # Options:
+ # * :message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /number/
+ #
+ # Example:
+ # should_only_allow_numeric_values_for :age
+ #
+ def should_only_allow_numeric_values_for(*attributes)
+ message = get_options!(attributes, :message)
+ message ||= /number/
+ klass = model_class
+ attributes.each do |attribute|
+ attribute = attribute.to_sym
+ should "only allow numeric values for #{attribute}" do
+ assert object = klass.find(:first), "Can't find first #{klass}"
+ object.send(:"#{attribute}=", "abcd")
+ assert !object.valid?, "Instance is still valid"
+ assert_contains(object.errors.on(attribute), message)
+ end
+ end
+ end
+
+ # Ensures that the has_many relationship exists. Will also test that the
+ # associated table has the required columns. Works with polymorphic
+ # associations.
+ #
+ # Options:
+ # * :through - association name for has_many :through
+ # * :dependent - tests that the association makes use of the dependent option.
+ #
+ # Example:
+ # should_have_many :friends
+ # should_have_many :enemies, :through => :friends
+ # should_have_many :enemies, :dependent => :destroy
+ #
+ def should_have_many(*associations)
+ through, dependent = get_options!(associations, :through, :dependent)
+ klass = model_class
+ associations.each do |association|
+ name = "have many #{association}"
+ name += " through #{through}" if through
+ name += " dependent => #{dependent}" if dependent
+ should name do
+ reflection = klass.reflect_on_association(association)
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
+ assert_equal :has_many, reflection.macro
+
+ associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
+
+ if through
+ through_reflection = klass.reflect_on_association(through)
+ assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
+ assert_equal(through, reflection.options[:through])
+ end
+
+ if dependent
+ assert_equal dependent.to_s,
+ reflection.options[:dependent].to_s,
+ "#{associated_klass.name} should have #{dependent} dependency"
+ end
+
+ # Check for the existence of the foreign key on the other table
+ unless reflection.options[:through]
if reflection.options[:foreign_key]
fk = reflection.options[:foreign_key]
elsif reflection.options[:as]
fk = reflection.options[:as].to_s.foreign_key
- fk_type = fk.gsub(/_id$/, '_type')
- assert associated_klass.column_names.include?(fk_type),
- "#{associated_klass.name} does not have a #{fk_type} column."
else
- fk = klass.name.foreign_key
+ fk = reflection.primary_key_name
end
- assert associated_klass.column_names.include?(fk.to_s),
- "#{associated_klass.name} does not have a #{fk} foreign key."
+
+ assert associated_klass.column_names.include?(fk.to_s),
+ "#{associated_klass.name} does not have a #{fk} foreign key."
end
end
end
-
- # Ensures that the has_and_belongs_to_many relationship exists, and that the join
- # table is in place.
- #
- # should_have_and_belong_to_many :posts, :cars
- #
- def should_have_and_belong_to_many(*associations)
- get_options!(associations)
- klass = model_class
+ end
- associations.each do |association|
- should "should have and belong to many #{association}" do
- reflection = klass.reflect_on_association(association)
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
- assert_equal :has_and_belongs_to_many, reflection.macro
- table = reflection.options[:join_table]
- assert ::ActiveRecord::Base.connection.tables.include?(table), "table #{table} doesn't exist"
- end
- end
- end
-
- # Ensure that the belongs_to relationship exists.
- #
- # should_belong_to :parent
- #
- def should_belong_to(*associations)
- get_options!(associations)
- klass = model_class
- associations.each do |association|
- should "belong_to #{association}" do
- reflection = klass.reflect_on_association(association)
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
- assert_equal :belongs_to, reflection.macro
+ # Ensure that the has_one relationship exists. Will also test that the
+ # associated table has the required columns. Works with polymorphic
+ # associations.
+ #
+ # Example:
+ # should_have_one :god # unless hindu
+ #
+ def should_have_one(*associations)
+ get_options!(associations)
+ klass = model_class
+ associations.each do |association|
+ should "have one #{association}" do
+ reflection = klass.reflect_on_association(association)
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
+ assert_equal :has_one, reflection.macro
+
+ associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
- unless reflection.options[:polymorphic]
- associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
- fk = reflection.options[:foreign_key] || reflection.primary_key_name
- assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key."
- end
- end
- end
- end
-
- # Ensure that the given class methods are defined on the model.
- #
- # should_have_class_methods :find, :destroy
- #
- def should_have_class_methods(*methods)
- get_options!(methods)
- klass = model_class
- methods.each do |method|
- should "respond to class method ##{method}" do
- assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
+ if reflection.options[:foreign_key]
+ fk = reflection.options[:foreign_key]
+ elsif reflection.options[:as]
+ fk = reflection.options[:as].to_s.foreign_key
+ fk_type = fk.gsub(/_id$/, '_type')
+ assert associated_klass.column_names.include?(fk_type),
+ "#{associated_klass.name} does not have a #{fk_type} column."
+ else
+ fk = klass.name.foreign_key
end
+ assert associated_klass.column_names.include?(fk.to_s),
+ "#{associated_klass.name} does not have a #{fk} foreign key."
end
end
+ end
- # Ensure that the given instance methods are defined on the model.
- #
- # should_have_instance_methods :email, :name, :name=
- #
- def should_have_instance_methods(*methods)
- get_options!(methods)
- klass = model_class
- methods.each do |method|
- should "respond to instance method ##{method}" do
- assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join
+ # table is in place.
+ #
+ # should_have_and_belong_to_many :posts, :cars
+ #
+ def should_have_and_belong_to_many(*associations)
+ get_options!(associations)
+ klass = model_class
+
+ associations.each do |association|
+ should "should have and belong to many #{association}" do
+ reflection = klass.reflect_on_association(association)
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
+ assert_equal :has_and_belongs_to_many, reflection.macro
+ table = reflection.options[:join_table]
+ assert ::ActiveRecord::Base.connection.tables.include?(table), "table #{table} doesn't exist"
+ end
+ end
+ end
+
+ # Ensure that the belongs_to relationship exists.
+ #
+ # should_belong_to :parent
+ #
+ def should_belong_to(*associations)
+ get_options!(associations)
+ klass = model_class
+ associations.each do |association|
+ should "belong_to #{association}" do
+ reflection = klass.reflect_on_association(association)
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
+ assert_equal :belongs_to, reflection.macro
+
+ unless reflection.options[:polymorphic]
+ associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
+ fk = reflection.options[:foreign_key] || reflection.primary_key_name
+ assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key."
end
end
end
-
- # Ensure that the given columns are defined on the models backing SQL table.
- #
- # should_have_db_columns :id, :email, :name, :created_at
- #
- def should_have_db_columns(*columns)
- column_type = get_options!(columns, :type)
- klass = model_class
- columns.each do |name|
- test_name = "have column #{name}"
- test_name += " of type #{column_type}" if column_type
- should test_name do
- column = klass.columns.detect {|c| c.name == name.to_s }
- assert column, "#{klass.name} does not have column #{name}"
- end
+ end
+
+ # Ensure that the given class methods are defined on the model.
+ #
+ # should_have_class_methods :find, :destroy
+ #
+ def should_have_class_methods(*methods)
+ get_options!(methods)
+ klass = model_class
+ methods.each do |method|
+ should "respond to class method ##{method}" do
+ assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
end
end
+ end
- # Ensure that the given column is defined on the models backing SQL table. The options are the same as
- # the instance variables defined on the column definition: :precision, :limit, :default, :null,
- # :primary, :type, :scale, and :sql_type.
- #
- # should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
- # :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
- #
- def should_have_db_column(name, opts = {})
- klass = model_class
- test_name = "have column named :#{name}"
- test_name += " with options " + opts.inspect unless opts.empty?
+ # Ensure that the given instance methods are defined on the model.
+ #
+ # should_have_instance_methods :email, :name, :name=
+ #
+ def should_have_instance_methods(*methods)
+ get_options!(methods)
+ klass = model_class
+ methods.each do |method|
+ should "respond to instance method ##{method}" do
+ assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
+ end
+ end
+ end
+
+ # Ensure that the given columns are defined on the models backing SQL table.
+ #
+ # should_have_db_columns :id, :email, :name, :created_at
+ #
+ def should_have_db_columns(*columns)
+ column_type = get_options!(columns, :type)
+ klass = model_class
+ columns.each do |name|
+ test_name = "have column #{name}"
+ test_name += " of type #{column_type}" if column_type
should test_name do
column = klass.columns.detect {|c| c.name == name.to_s }
assert column, "#{klass.name} does not have column #{name}"
- opts.each do |k, v|
- assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}"
- end
end
end
-
- # Ensures that there are DB indices on the given columns or tuples of columns.
- # Also aliased to should_have_index for readability
- #
- # should_have_indices :email, :name, [:commentable_type, :commentable_id]
- # should_have_index :age
- #
- def should_have_indices(*columns)
- table = model_class.name.tableize
- indices = ::ActiveRecord::Base.connection.indexes(table).map(&:columns)
-
- columns.each do |column|
- should "have index on #{table} for #{column.inspect}" do
- columns = [column].flatten.map(&:to_s)
- assert_contains(indices, columns)
- end
- end
- end
-
- alias_method :should_have_index, :should_have_indices
-
- # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
- #
- # Options:
- # * :message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /must be accepted/
- #
- # Example:
- # should_require_acceptance_of :eula
- #
- def should_require_acceptance_of(*attributes)
- message = get_options!(attributes, :message)
- message ||= /must be accepted/
- klass = model_class
-
- attributes.each do |attribute|
- should "require #{attribute} to be accepted" do
- object = klass.new
- object.send("#{attribute}=", false)
-
- assert !object.valid?, "#{klass.name} does not require acceptance of #{attribute}."
- assert object.errors.on(attribute), "#{klass.name} does not require acceptance of #{attribute}."
- assert_contains(object.errors.on(attribute), message)
- end
- end
- end
-
- private
-
- include ThoughtBot::Shoulda::Private
end
+
+ # Ensure that the given column is defined on the models backing SQL table. The options are the same as
+ # the instance variables defined on the column definition: :precision, :limit, :default, :null,
+ # :primary, :type, :scale, and :sql_type.
+ #
+ # should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
+ # :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
+ #
+ def should_have_db_column(name, opts = {})
+ klass = model_class
+ test_name = "have column named :#{name}"
+ test_name += " with options " + opts.inspect unless opts.empty?
+ should test_name do
+ column = klass.columns.detect {|c| c.name == name.to_s }
+ assert column, "#{klass.name} does not have column #{name}"
+ opts.each do |k, v|
+ assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}"
+ end
+ end
+ end
+
+ # Ensures that there are DB indices on the given columns or tuples of columns.
+ # Also aliased to should_have_index for readability
+ #
+ # should_have_indices :email, :name, [:commentable_type, :commentable_id]
+ # should_have_index :age
+ #
+ def should_have_indices(*columns)
+ table = model_class.name.tableize
+ indices = ::ActiveRecord::Base.connection.indexes(table).map(&:columns)
+
+ columns.each do |column|
+ should "have index on #{table} for #{column.inspect}" do
+ columns = [column].flatten.map(&:to_s)
+ assert_contains(indices, columns)
+ end
+ end
+ end
+
+ alias_method :should_have_index, :should_have_indices
+
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
+ #
+ # Options:
+ # * :message - value the test expects to find in errors.on(:attribute).
+ # Regexp or string. Default = /must be accepted/
+ #
+ # Example:
+ # should_require_acceptance_of :eula
+ #
+ def should_require_acceptance_of(*attributes)
+ message = get_options!(attributes, :message)
+ message ||= /must be accepted/
+ klass = model_class
+
+ attributes.each do |attribute|
+ should "require #{attribute} to be accepted" do
+ object = klass.new
+ object.send("#{attribute}=", false)
+
+ assert !object.valid?, "#{klass.name} does not require acceptance of #{attribute}."
+ assert object.errors.on(attribute), "#{klass.name} does not require acceptance of #{attribute}."
+ assert_contains(object.errors.on(attribute), message)
+ end
+ end
+ end
+
+ private
+
+ include Shoulda::Private
end
end
diff --git a/lib/shoulda/color.rb b/lib/shoulda/color.rb
index 1ccfad2b..1324dd7b 100644
--- a/lib/shoulda/color.rb
+++ b/lib/shoulda/color.rb
@@ -9,7 +9,7 @@ require 'test/unit/ui/console/testrunner'
# every rake task, as though there was another (empty) set of tests.
# A fix would be most welcome.
#
-module ThoughtBot::Shoulda::Color
+module Shoulda::Color
COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 } # :nodoc:
def self.method_missing(color_name, *args) # :nodoc:
color(color_name) + args.first + color(:clear)
@@ -25,7 +25,7 @@ module Test # :nodoc:
alias :old_to_s :to_s
def to_s
if old_to_s =~ /\d+ tests, \d+ assertions, (\d+) failures, (\d+) errors/
- ThoughtBot::Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&)
+ Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&)
end
end
end
@@ -43,16 +43,16 @@ module Test # :nodoc:
class Failure # :nodoc:
alias :old_long_display :long_display
def long_display
- # old_long_display.sub('Failure', ThoughtBot::Shoulda::Color.red('Failure'))
- ThoughtBot::Shoulda::Color.red(old_long_display)
+ # old_long_display.sub('Failure', Shoulda::Color.red('Failure'))
+ Shoulda::Color.red(old_long_display)
end
end
class Error # :nodoc:
alias :old_long_display :long_display
def long_display
- # old_long_display.sub('Error', ThoughtBot::Shoulda::Color.yellow('Error'))
- ThoughtBot::Shoulda::Color.yellow(old_long_display)
+ # old_long_display.sub('Error', Shoulda::Color.yellow('Error'))
+ Shoulda::Color.yellow(old_long_display)
end
end
@@ -62,9 +62,9 @@ module Test # :nodoc:
def output_single(something, level=NORMAL)
return unless (output?(level))
something = case something
- when '.' then ThoughtBot::Shoulda::Color.green('.')
- when 'F' then ThoughtBot::Shoulda::Color.red("F")
- when 'E' then ThoughtBot::Shoulda::Color.yellow("E")
+ when '.' then Shoulda::Color.green('.')
+ when 'F' then Shoulda::Color.red("F")
+ when 'E' then Shoulda::Color.yellow("E")
else something
end
@io.write(something)
diff --git a/lib/shoulda/controller_tests/controller_tests.rb b/lib/shoulda/controller_tests/controller_tests.rb
index f0059c0f..c5b1d5d2 100644
--- a/lib/shoulda/controller_tests/controller_tests.rb
+++ b/lib/shoulda/controller_tests/controller_tests.rb
@@ -1,467 +1,464 @@
-module ThoughtBot # :nodoc:
- module Shoulda # :nodoc:
- module Controller
- def self.included(other) # :nodoc:
- other.class_eval do
- extend ThoughtBot::Shoulda::Controller::ClassMethods
- include ThoughtBot::Shoulda::Controller::InstanceMethods
- ThoughtBot::Shoulda::Controller::ClassMethods::VALID_FORMATS.each do |format|
- include "ThoughtBot::Shoulda::Controller::#{format.to_s.upcase}".constantize
- end
+module Shoulda # :nodoc:
+ module Controller
+ def self.included(other) # :nodoc:
+ other.class_eval do
+ extend Shoulda::Controller::ClassMethods
+ include Shoulda::Controller::InstanceMethods
+ Shoulda::Controller::ClassMethods::VALID_FORMATS.each do |format|
+ include "Shoulda::Controller::#{format.to_s.upcase}".constantize
end
end
-
- # = Macro test helpers for your controllers
- #
- # By using the macro helpers you can quickly and easily create concise and easy to read test suites.
+ end
+
+ # = Macro test helpers for your controllers
+ #
+ # By using the macro helpers you can quickly and easily create concise and easy to read test suites.
+ #
+ # This code segment:
+ # context "on GET to :show for first record" do
+ # setup do
+ # get :show, :id => 1
+ # end
+ #
+ # should_assign_to :user
+ # should_respond_with :success
+ # should_render_template :show
+ # should_not_set_the_flash
+ #
+ # should "do something else really cool" do
+ # assert_equal 1, assigns(:user).id
+ # end
+ # end
+ #
+ # Would produce 5 tests for the +show+ action
+ #
+ # Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your
+ # controller responds restfully to a variety of requested formats.
+ module ClassMethods
+ # Formats tested by #should_be_restful. Defaults to [:html, :xml]
+ VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
+ VALID_FORMATS.each {|f| require "shoulda/controller_tests/formats/#{f}.rb"}
+
+ # Actions tested by #should_be_restful
+ VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
+
+ # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
#
- # This code segment:
- # context "on GET to :show for first record" do
- # setup do
- # get :show, :id => 1
+ # Example:
+ # class UsersControllerTest < Test::Unit::TestCase
+ # load_all_fixtures
+ #
+ # def setup
+ # ...normal setup code...
+ # @user = User.find(:first)
# end
#
- # should_assign_to :user
- # should_respond_with :success
- # should_render_template :show
- # should_not_set_the_flash
- #
- # should "do something else really cool" do
- # assert_equal 1, assigns(:user).id
+ # should_be_restful do |resource|
+ # resource.identifier = :id
+ # resource.klass = User
+ # resource.object = :user
+ # resource.parent = []
+ # resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
+ # resource.formats = [:html, :xml]
+ #
+ # resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
+ # resource.update.params = { :name => "sue" }
+ #
+ # resource.create.redirect = "user_url(@user)"
+ # resource.update.redirect = "user_url(@user)"
+ # resource.destroy.redirect = "users_url"
+ #
+ # resource.create.flash = /created/i
+ # resource.update.flash = /updated/i
+ # resource.destroy.flash = /removed/i
# end
# end
#
- # Would produce 5 tests for the +show+ action
+ # Whenever possible, the resource attributes will be set to sensible defaults.
#
- # Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your
- # controller responds restfully to a variety of requested formats.
- module ClassMethods
- # Formats tested by #should_be_restful. Defaults to [:html, :xml]
- VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
- VALID_FORMATS.each {|f| require "shoulda/controller_tests/formats/#{f}.rb"}
+ class ResourceOptions
+ # Configuration options for the create, update, destroy actions under should_be_restful
+ class ActionOptions
+ # String evaled to get the target of the redirection.
+ # All of the instance variables set by the controller will be available to the
+ # evaled code.
+ #
+ # Example:
+ # resource.create.redirect = "user_url(@user.company, @user)"
+ #
+ # Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
+ attr_accessor :redirect
- # Actions tested by #should_be_restful
- VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
+ # String or Regexp describing a value expected in the flash. Will match against any flash key.
+ #
+ # Defaults:
+ # destroy:: /removed/
+ # create:: /created/
+ # update:: /updated/
+ attr_accessor :flash
+
+ # Hash describing the params that should be sent in with this action.
+ attr_accessor :params
+ end
- # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
- #
+ # Configuration options for the denied actions under should_be_restful
+ #
# Example:
- # class UsersControllerTest < Test::Unit::TestCase
- # load_all_fixtures
- #
- # def setup
- # ...normal setup code...
- # @user = User.find(:first)
+ # context "The public" do
+ # setup do
+ # @request.session[:logged_in] = false
# end
#
# should_be_restful do |resource|
- # resource.identifier = :id
- # resource.klass = User
- # resource.object = :user
- # resource.parent = []
- # resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
- # resource.formats = [:html, :xml]
+ # resource.parent = :user
#
- # resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
- # resource.update.params = { :name => "sue" }
- #
- # resource.create.redirect = "user_url(@user)"
- # resource.update.redirect = "user_url(@user)"
- # resource.destroy.redirect = "users_url"
- #
- # resource.create.flash = /created/i
- # resource.update.flash = /updated/i
- # resource.destroy.flash = /removed/i
+ # resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
+ # resource.denied.flash = /get outta here/i
+ # resource.denied.redirect = 'new_session_url'
+ # end
+ # end
+ #
+ class DeniedOptions
+ # String evaled to get the target of the redirection.
+ # All of the instance variables set by the controller will be available to the
+ # evaled code.
+ #
+ # Example:
+ # resource.create.redirect = "user_url(@user.company, @user)"
+ attr_accessor :redirect
+
+ # String or Regexp describing a value expected in the flash. Will match against any flash key.
+ #
+ # Example:
+ # resource.create.flash = /created/
+ attr_accessor :flash
+
+ # Actions that should be denied (only used by resource.denied). Note that these actions will
+ # only be tested if they are also listed in +resource.actions+
+ # The special value of :all will deny all of the REST actions.
+ attr_accessor :actions
+ end
+
+ # Name of key in params that references the primary key.
+ # Will almost always be :id (default), unless you are using a plugin or have patched rails.
+ attr_accessor :identifier
+
+ # Name of the ActiveRecord class this resource is responsible for. Automatically determined from
+ # test class if not explicitly set. UserTest => "User"
+ attr_accessor :klass
+
+ # Name of the instantiated ActiveRecord object that should be used by some of the tests.
+ # Defaults to the underscored name of the AR class. CompanyManager => :company_manager
+ attr_accessor :object
+
+ # Name of the parent AR objects. Can be set as parent= or parents=, and can take either
+ # the name of the parent resource (if there's only one), or an array of names (if there's
+ # more than one).
+ #
+ # Example:
+ # # in the routes...
+ # map.resources :companies do
+ # map.resources :people do
+ # map.resources :limbs
# end
# end
#
- # Whenever possible, the resource attributes will be set to sensible defaults.
- #
- class ResourceOptions
- # Configuration options for the create, update, destroy actions under should_be_restful
- class ActionOptions
- # String evaled to get the target of the redirection.
- # All of the instance variables set by the controller will be available to the
- # evaled code.
- #
- # Example:
- # resource.create.redirect = "user_url(@user.company, @user)"
- #
- # Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
- attr_accessor :redirect
-
- # String or Regexp describing a value expected in the flash. Will match against any flash key.
- #
- # Defaults:
- # destroy:: /removed/
- # create:: /created/
- # update:: /updated/
- attr_accessor :flash
-
- # Hash describing the params that should be sent in with this action.
- attr_accessor :params
- end
-
- # Configuration options for the denied actions under should_be_restful
- #
- # Example:
- # context "The public" do
- # setup do
- # @request.session[:logged_in] = false
- # end
- #
- # should_be_restful do |resource|
- # resource.parent = :user
- #
- # resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
- # resource.denied.flash = /get outta here/i
- # resource.denied.redirect = 'new_session_url'
- # end
- # end
- #
- class DeniedOptions
- # String evaled to get the target of the redirection.
- # All of the instance variables set by the controller will be available to the
- # evaled code.
- #
- # Example:
- # resource.create.redirect = "user_url(@user.company, @user)"
- attr_accessor :redirect
-
- # String or Regexp describing a value expected in the flash. Will match against any flash key.
- #
- # Example:
- # resource.create.flash = /created/
- attr_accessor :flash
-
- # Actions that should be denied (only used by resource.denied). Note that these actions will
- # only be tested if they are also listed in +resource.actions+
- # The special value of :all will deny all of the REST actions.
- attr_accessor :actions
- end
-
- # Name of key in params that references the primary key.
- # Will almost always be :id (default), unless you are using a plugin or have patched rails.
- attr_accessor :identifier
-
- # Name of the ActiveRecord class this resource is responsible for. Automatically determined from
- # test class if not explicitly set. UserTest => "User"
- attr_accessor :klass
-
- # Name of the instantiated ActiveRecord object that should be used by some of the tests.
- # Defaults to the underscored name of the AR class. CompanyManager => :company_manager
- attr_accessor :object
-
- # Name of the parent AR objects. Can be set as parent= or parents=, and can take either
- # the name of the parent resource (if there's only one), or an array of names (if there's
- # more than one).
- #
- # Example:
- # # in the routes...
- # map.resources :companies do
- # map.resources :people do
- # map.resources :limbs
- # end
- # end
- #
- # # in the tests...
- # class PeopleControllerTest < Test::Unit::TestCase
- # should_be_restful do |resource|
- # resource.parent = :companies
- # end
- # end
- #
- # class LimbsControllerTest < Test::Unit::TestCase
- # should_be_restful do |resource|
- # resource.parents = [:companies, :people]
- # end
- # end
- attr_accessor :parent
- alias parents parent
- alias parents= parent=
-
- # Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
- # Tests for each actionw will only be generated if the action is listed here.
- # The special value of :all will test all of the REST actions.
- #
- # Example (for a read-only controller):
- # resource.actions = [:show, :index]
- attr_accessor :actions
-
- # Formats that should be tested. Must be a subset of VALID_FORMATS (default).
- # Each action will be tested against the formats listed here. The special value
- # of :all will test all of the supported formats.
- #
- # Example:
- # resource.actions = [:html, :xml]
- attr_accessor :formats
-
- # ActionOptions object specifying options for the create action.
- attr_accessor :create
-
- # ActionOptions object specifying options for the update action.
- attr_accessor :update
-
- # ActionOptions object specifying options for the desrtoy action.
- attr_accessor :destroy
-
- # DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
- attr_accessor :denied
-
- def initialize # :nodoc:
- @create = ActionOptions.new
- @update = ActionOptions.new
- @destroy = ActionOptions.new
- @denied = DeniedOptions.new
-
- @create.flash ||= /created/i
- @update.flash ||= /updated/i
- @destroy.flash ||= /removed/i
- @denied.flash ||= /denied/i
-
- @create.params ||= {}
- @update.params ||= {}
-
- @actions = VALID_ACTIONS
- @formats = VALID_FORMATS
- @denied.actions = []
- end
-
- def normalize!(target) # :nodoc:
- @denied.actions = VALID_ACTIONS if @denied.actions == :all
- @actions = VALID_ACTIONS if @actions == :all
- @formats = VALID_FORMATS if @formats == :all
-
- @denied.actions = @denied.actions.map(&:to_sym)
- @actions = @actions.map(&:to_sym)
- @formats = @formats.map(&:to_sym)
-
- ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
- ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
- ensure_valid_members(@formats, VALID_FORMATS, 'formats')
-
- @identifier ||= :id
- @klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
- @object ||= @klass.name.tableize.singularize
- @parent ||= []
- @parent = [@parent] unless @parent.is_a? Array
-
- collection_helper = [@parent, @object.to_s.pluralize, 'url'].flatten.join('_')
- collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
- @destroy.redirect ||= "#{collection_helper}(#{collection_args})"
-
- member_helper = [@parent, @object, 'url'].flatten.join('_')
- member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
- @create.redirect ||= "#{member_helper}(#{member_args})"
- @update.redirect ||= "#{member_helper}(#{member_args})"
- @denied.redirect ||= "new_session_url"
- end
-
- private
-
- def ensure_valid_members(ary, valid_members, name) # :nodoc:
- invalid = ary - valid_members
- raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
- end
- end
-
- # :section: should_be_restful
- # Generates a full suite of tests for a restful controller.
- #
- # The following definition will generate tests for the +index+, +show+, +new+,
- # +edit+, +create+, +update+ and +destroy+ actions, in both +html+ and +xml+ formats:
- #
- # should_be_restful do |resource|
- # resource.parent = :user
- #
- # resource.create.params = { :title => "first post", :body => 'blah blah blah'}
- # resource.update.params = { :title => "changed" }
+ # # in the tests...
+ # class PeopleControllerTest < Test::Unit::TestCase
+ # should_be_restful do |resource|
+ # resource.parent = :companies
+ # end
# end
#
- # This generates about 40 tests, all of the format:
- # "on GET to :show should assign @user."
- # "on GET to :show should not set the flash."
- # "on GET to :show should render 'show' template."
- # "on GET to :show should respond with success."
- # "on GET to :show as xml should assign @user."
- # "on GET to :show as xml should have ContentType set to 'application/xml'."
- # "on GET to :show as xml should respond with success."
- # "on GET to :show as xml should return