diff --git a/README.rdoc b/README.rdoc
index 80f8171d..13f7a008 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 (Shoulda::Context)
+=== Context Helpers (ThoughtBot::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 (Shoulda::ActiveRecord)
+=== ActiveRecord Tests (ThoughtBot::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 (Shoulda::Controller::ClassMethods)
+=== Controller Tests (ThoughtBot::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 (Shoulda::General)
+=== Helpful Assertions (ThoughtBot::Shoulda::General)
More to come here, but have fun with what's there.
diff --git a/Rakefile b/Rakefile
index 4bb9a7ea..52c47ecf 100644
--- a/Rakefile
+++ b/Rakefile
@@ -6,7 +6,7 @@ require 'rake/rdoctask'
Rake::TestTask.new do |t|
t.libs << 'lib'
- t.pattern = 'test/**/*_test.rb'
+ t.pattern = 'test/{unit,functional,other}/**/*_test.rb'
t.verbose = false
end
diff --git a/lib/shoulda.rb b/lib/shoulda.rb
index 56801971..a90ff183 100644
--- a/lib/shoulda.rb
+++ b/lib/shoulda.rb
@@ -1,144 +1,43 @@
-require 'shoulda/context'
+require 'shoulda/gem/shoulda'
require 'shoulda/private_helpers'
require 'shoulda/general'
+require 'shoulda/active_record_helpers'
+require 'shoulda/controller_tests/controller_tests.rb'
require 'yaml'
-require 'shoulda/active_record_helpers' if defined?(ActiveRecord)
-require 'shoulda/controller_tests/controller_tests.rb' if defined?(ActionController)
-
-config_files = []
-config_files << "shoulda.conf"
-config_files << File.join("test", "shoulda.conf")
-config_files << File.join(RAILS_ROOT, "test", "shoulda.conf") if defined?(RAILS_ROOT)
-config_files << File.join(ENV["HOME"], ".shoulda.conf") if ENV["HOME"]
-
shoulda_options = {}
-config_files.each do |file|
- shoulda_options.merge!(YAML.load_file(file).symbolize_keys) if File.exists? file
+
+possible_config_paths = []
+possible_config_paths << File.join(ENV["HOME"], ".shoulda.conf") if ENV["HOME"]
+possible_config_paths << "shoulda.conf"
+possible_config_paths << File.join("test", "shoulda.conf")
+possible_config_paths << File.join(RAILS_ROOT, "test", "shoulda.conf") if defined?(RAILS_ROOT)
+
+possible_config_paths.each do |config_file|
+ if File.exists? config_file
+ shoulda_options = YAML.load_file(config_file).symbolize_keys
+ break
+ end
end
require 'shoulda/color' if shoulda_options[:color]
-module Shoulda
- class << self
- attr_accessor :current_context
- end
-
- # Should statements are just syntactic sugar over normal Test::Unit test methods. A should block
- # contains all the normal code and assertions you're used to seeing, with the added benefit that
- # they can be wrapped inside context blocks (see below).
- #
- # == Example:
- #
- # class UserTest << Test::Unit::TestCase
- #
- # def setup
- # @user = User.new("John", "Doe")
- # end
- #
- # should "return its full name"
- # assert_equal 'John Doe', @user.full_name
- # end
- #
- # end
- #
- # ...will produce the following test:
- # * "test: User should return its full name. "
- #
- # Note: The part before should in the test name is gleamed from the name of the Test::Unit class.
-
- def should(name, &blk)
- should_eventually(name) && return unless block_given?
-
- if Shoulda.current_context
- Shoulda.current_context.should(name, &blk)
- else
- context_name = self.name.gsub(/Test/, "")
- context = Shoulda::Context.new(context_name, self) do
- should(name, &blk)
- end
- context.build
- end
- end
-
- # Just like should, but never runs, and instead prints a differed message in the Test::Unit output.
- def should_eventually(name, &blk)
- context_name = self.name.gsub(/Test/, "")
- context = Shoulda::Context.new(context_name, self) do
- should_eventually(name, &blk)
- end
- context.build
- end
-
- # A context block groups should statements under a common set of setup/teardown methods.
- # Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
- # and readability of your test code.
- #
- # A context block can contain setup, should, should_eventually, and teardown blocks.
- #
- # class UserTest << Test::Unit::TestCase
- # context "A User instance" do
- # setup do
- # @user = User.find(:first)
- # end
- #
- # should "return its full name"
- # assert_equal 'John Doe', @user.full_name
- # end
- # end
- # end
- #
- # This code will produce the method "test: A User instance should return its full name. ".
- #
- # Contexts may be nested. Nested contexts run their setup blocks from out to in before each
- # should statement. They then run their teardown blocks from in to out after each should statement.
- #
- # class UserTest << Test::Unit::TestCase
- # context "A User instance" do
- # setup do
- # @user = User.find(:first)
- # end
- #
- # should "return its full name"
- # assert_equal 'John Doe', @user.full_name
- # end
- #
- # context "with a profile" do
- # setup do
- # @user.profile = Profile.find(:first)
- # end
- #
- # should "return true when sent :has_profile?"
- # assert @user.has_profile?
- # end
- # end
- # end
- # end
- #
- # This code will produce the following methods
- # * "test: A User instance should return its full name. "
- # * "test: A User instance with a profile should return true when sent :has_profile?. "
- #
- # Just like should statements, a context block can exist next to normal def test_the_old_way; end
- # tests. This means you do not have to fully commit to the context/should syntax in a test file.
-
- def context(name, &blk)
- if Shoulda.current_context
- Shoulda.current_context.context(name, &blk)
- else
- context = Shoulda::Context.new(name, self, &blk)
- context.build
- end
- end
-end
-
module Test # :nodoc: all
module Unit
class TestCase
- include Shoulda::General
- include Shoulda::Controller
- extend Shoulda::ActiveRecord
+
+ include ThoughtBot::Shoulda::General
+ include ThoughtBot::Shoulda::Controller
+
+ extend ThoughtBot::Shoulda::ActiveRecord
end
end
end
+module ActionController #:nodoc: all
+ module Integration
+ class Session
+ include ThoughtBot::Shoulda::General
+ end
+ end
+end
diff --git a/lib/shoulda/active_record_helpers.rb b/lib/shoulda/active_record_helpers.rb
index 6b6308cb..ec840571 100644
--- a/lib/shoulda/active_record_helpers.rb
+++ b/lib/shoulda/active_record_helpers.rb
@@ -1,602 +1,604 @@
-module Shoulda
- # = 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.
+module ThoughtBot # :nodoc:
+ module Shoulda # :nodoc:
+ # = Macro test helpers for your active record models
#
- # Options:
- # * :message - value the test expects to find in errors.on(:attribute).
- # Regexp or string. Default = /blank/
+ # These helpers will test most of the validations and associations for your ActiveRecord models.
#
- # Example:
- # should_require_attributes :name, :phone_number
+ # 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
#
- 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)
+ # 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
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
- 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)
+ # 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
- 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)}")
+ 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
- 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")
+ 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
- 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
+ # 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)
+ 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), message, "when set to \"#{v}\"")
+ assert_contains(object.errors.on(attribute), low_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
+ 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_nil object.errors.on(attribute)
+ assert_does_not_contain(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
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)
+ 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}=", 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}\"")
+ 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
- end
- if min_length > 0
- should "allow #{attribute} to be exactly #{min_length} chars long" do
- min_value = "x" * min_length
+ should "allow #{attribute} to be #{max}" do
+ v = max
assert object = klass.find(:first), "Can't find first #{klass}"
- object.send("#{attribute}=", min_value)
+ object.send("#{attribute}=", v)
object.save
- assert_does_not_contain(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
+ assert_does_not_contain(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
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
-
- 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/
+ 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
-
- 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
-
- 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)
+ # 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
- 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
+ # 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
+ 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])
+ 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
+ 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
- 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 = reflection.primary_key_name
+ 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."
+ assert associated_klass.column_names.include?(fk.to_s),
+ "#{associated_klass.name} does not have a #{fk} foreign key."
end
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
- # 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
-
- 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
-
- # 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."
+ 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
- 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}"
+
+ # 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
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}"
+
+ # 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
- 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
+ # 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}"
+ 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?
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
- 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}"
+ # 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
- 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
+ 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
- # 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)
+ 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)
+ 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
-
- private
-
- include Shoulda::Private
end
end
diff --git a/lib/shoulda/color.rb b/lib/shoulda/color.rb
index 170ae2c1..1ccfad2b 100644
--- a/lib/shoulda/color.rb
+++ b/lib/shoulda/color.rb
@@ -1,8 +1,6 @@
require 'test/unit/ui/console/testrunner'
-# Completely stolen from redgreen gem (thanks Pat Eyler and Chris Wanstrath).
-#
-# NOTE: Is this now in autotest? Can I remove this, then?
+# Completely stolen from redgreen gem
#
# Adds colored output to your tests. Specify color: true in
# your ~/.shoulda.conf file to enable.
@@ -11,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 Shoulda::Color
+module ThoughtBot::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)
@@ -27,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/
- Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&)
+ ThoughtBot::Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&)
end
end
end
@@ -45,16 +43,16 @@ module Test # :nodoc:
class Failure # :nodoc:
alias :old_long_display :long_display
def long_display
- # old_long_display.sub('Failure', Shoulda::Color.red('Failure'))
- Shoulda::Color.red(old_long_display)
+ # old_long_display.sub('Failure', ThoughtBot::Shoulda::Color.red('Failure'))
+ ThoughtBot::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', Shoulda::Color.yellow('Error'))
- Shoulda::Color.yellow(old_long_display)
+ # old_long_display.sub('Error', ThoughtBot::Shoulda::Color.yellow('Error'))
+ ThoughtBot::Shoulda::Color.yellow(old_long_display)
end
end
@@ -64,9 +62,9 @@ module Test # :nodoc:
def output_single(something, level=NORMAL)
return unless (output?(level))
something = case something
- when '.' then Shoulda::Color.green('.')
- when 'F' then Shoulda::Color.red("F")
- when 'E' then Shoulda::Color.yellow("E")
+ when '.' then ThoughtBot::Shoulda::Color.green('.')
+ when 'F' then ThoughtBot::Shoulda::Color.red("F")
+ when 'E' then ThoughtBot::Shoulda::Color.yellow("E")
else something
end
@io.write(something)
diff --git a/lib/shoulda/context.rb b/lib/shoulda/context.rb
deleted file mode 100644
index 3520ae5c..00000000
--- a/lib/shoulda/context.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-require File.join(File.dirname(__FILE__), 'extensions', 'proc')
-
-module Shoulda
- class Context
- attr_accessor :name # my name
- attr_accessor :parent # may be another context, or the original test::unit class.
- attr_accessor :subcontexts # array of contexts nested under myself
- attr_accessor :setup_block # block given via a setup method
- attr_accessor :teardown_block # block given via a teardown method
- attr_accessor :shoulds # array of hashes representing the should statements
- attr_accessor :should_eventuallys # array of hashes representing the should eventually statements
-
- def initialize(name, parent, &blk)
- Shoulda.current_context = self
- self.name = name
- self.parent = parent
- self.setup_block = nil
- self.teardown_block = nil
- self.shoulds = []
- self.should_eventuallys = []
- self.subcontexts = []
-
- blk.bind(self).call
- Shoulda.current_context = nil
- end
-
- # Creates a context. See Shoulda#context.
- def context(name, &blk)
- subcontexts << Context.new(name, self, &blk)
- Shoulda.current_context = self
- end
-
- # Creates a should statement. See Shoulda#should.
- def should(name, &blk)
- self.shoulds << { :name => name, :block => blk }
- end
-
- # Creates a should_eventually statement. See Shoulda#should_eventually.
- def should_eventually(name, &blk)
- self.should_eventuallys << { :name => name, :block => blk }
- end
-
- # Any code in a setup block will be run before the should statements in a
- # context. Nested contexts will have their setup blocks run in order.
- def setup(&blk)
- self.setup_block = blk
- end
-
- # Any code in a teardown block will be run after the should statements in a
- # context. Nested contexts will have their teardown blocks run in reverse
- # order.
- def teardown(&blk)
- self.teardown_block = blk
- end
-
- # The full name of this context, including parents.
- def full_name
- parent_name = parent.full_name if subcontext?
- return [parent_name, name].join(" ").strip
- end
-
- # Returns true if this context is nested
- def subcontext?
- parent.is_a?(self.class) # my parent is the same class as myself.
- end
-
- # Returns the root class that decends from Test::Unit.
- def test_unit_class
- subcontext? ? parent.test_unit_class : parent
- end
-
-
- # Creates a single test from a should hash
- def create_test_from_should_hash(should)
- test_name = ["test:", full_name, "should", "#{should[:name]}. "].flatten.join(' ').to_sym
-
- if test_unit_class.instance_methods.include?(test_name.to_s)
- warn " * WARNING: '#{test_name}' is already defined"
- end
-
- context = self
- test_unit_class.send(:define_method, test_name) do |*args|
- begin
- context.run_all_setup_blocks(self)
- should[:block].bind(self).call
- ensure
- context.run_all_teardown_blocks(self)
- end
- end
- end
-
- # Runs all the setup blocks in order
- def run_all_setup_blocks(binding)
- self.parent.run_all_setup_blocks(binding) if subcontext?
- setup_block.bind(binding).call if setup_block
- end
-
- # Runs all the teardown blocks in reverse order
- def run_all_teardown_blocks(binding)
- teardown_block.bind(binding).call if teardown_block
- self.parent.run_all_teardown_blocks(binding) if subcontext?
- end
-
- # Prints the should_eventually names to stdout
- def print_should_eventuallys
- should_eventuallys.each do |should|
- test_name = [full_name, "should", "#{should[:name]}. "].flatten.join(' ')
- puts " * DEFERRED: " + test_name
- end
- subcontexts.each { |context| context.print_should_eventuallys }
- end
-
- # Triggers the test method creation process, and prints the unimplemented tests.
- def build
- shoulds.each do |should|
- create_test_from_should_hash(should)
- end
-
- subcontexts.each { |context| context.build }
-
- print_should_eventuallys
- end
-
- # This delegates all method calls inside a context to the surrounding
- # Test::Unit class. This allows us to call Test::Unit macros inside a
- # context.
- def method_missing(method, *args, &blk)
- test_unit_class.send(method, *args, &blk)
- end
- end
-end
-
-module Test # :nodoc: all
- module Unit
- class TestCase
- extend Shoulda
- end
- end
-end
-
diff --git a/lib/shoulda/controller_tests/controller_tests.rb b/lib/shoulda/controller_tests/controller_tests.rb
index 7478f026..f0059c0f 100644
--- a/lib/shoulda/controller_tests/controller_tests.rb
+++ b/lib/shoulda/controller_tests/controller_tests.rb
@@ -1,472 +1,467 @@
-module Shoulda
- 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
+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
end
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.
- #
- # 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.
+
+ # = Macro test helpers for your controllers
+ #
+ # By using the macro helpers you can quickly and easily create concise and easy to read test suites.
#
- # Example:
- # class UsersControllerTest < Test::Unit::TestCase
- # load_all_fixtures
- #
- # def setup
- # ...normal setup code...
- # @user = User.find(:first)
+ # This code segment:
+ # context "on GET to :show for first record" do
+ # setup do
+ # get :show, :id => 1
# 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.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
+ # 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
#
- # Whenever possible, the resource attributes will be set to sensible defaults.
+ # Would produce 5 tests for the +show+ action
#
- 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
+ # 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"}
- # 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
+ # Actions tested by #should_be_restful
+ VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
- # Configuration options for the denied actions under should_be_restful
- #
+ # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
+ #
# Example:
- # context "The public" do
- # setup do
- # @request.session[:logged_in] = false
+ # class UsersControllerTest < Test::Unit::TestCase
+ # load_all_fixtures
+ #
+ # def setup
+ # ...normal setup code...
+ # @user = User.find(:first)
# end
#
# should_be_restful do |resource|
- # resource.parent = :user
+ # 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.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
- # resource.denied.flash = /get outta here/i
- # resource.denied.redirect = 'new_session_url'
- # end
+ # 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
#
- 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.
+ # 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:
- # resource.create.redirect = "user_url(@user.company, @user)"
- attr_accessor :redirect
+ # 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.
+ # 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:
- # 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.
+ # # 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
- # 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).
+ # :section: should_be_restful
+ # Generates a full suite of tests for a restful controller.
#
- # Example:
- # # in the routes...
- # map.resources :companies do
- # map.resources :people do
- # map.resources :limbs
- # end
+ # 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" }
# end
#
- # # 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