rmm5t-shoulda 2.0.6 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. data/README.rdoc +38 -10
  2. data/Rakefile +5 -3
  3. data/lib/shoulda.rb +7 -15
  4. data/lib/shoulda/action_mailer.rb +1 -1
  5. data/lib/shoulda/action_mailer/assertions.rb +32 -33
  6. data/lib/shoulda/active_record.rb +6 -2
  7. data/lib/shoulda/active_record/assertions.rb +62 -81
  8. data/lib/shoulda/active_record/helpers.rb +40 -0
  9. data/lib/shoulda/active_record/macros.rb +511 -640
  10. data/lib/shoulda/active_record/matchers.rb +42 -0
  11. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  12. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
  13. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  14. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  15. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  16. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  17. data/lib/shoulda/active_record/matchers/have_index_matcher.rb +105 -0
  18. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +125 -0
  19. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  20. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  21. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  22. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  23. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  24. data/lib/shoulda/active_record/matchers/validation_matcher.rb +56 -0
  25. data/lib/shoulda/assertions.rb +50 -40
  26. data/lib/shoulda/autoload_macros.rb +46 -0
  27. data/lib/shoulda/context.rb +124 -126
  28. data/lib/shoulda/helpers.rb +5 -7
  29. data/lib/shoulda/macros.rb +63 -64
  30. data/lib/shoulda/private_helpers.rb +16 -18
  31. data/lib/shoulda/rails.rb +5 -11
  32. data/lib/shoulda/rspec.rb +9 -0
  33. data/lib/shoulda/tasks/list_tests.rake +6 -1
  34. data/lib/shoulda/test_unit.rb +19 -0
  35. data/rails/init.rb +7 -1
  36. data/test/README +2 -2
  37. data/test/fail_macros.rb +16 -16
  38. data/test/functional/posts_controller_test.rb +33 -24
  39. data/test/functional/users_controller_test.rb +0 -19
  40. data/test/model_builder.rb +106 -0
  41. data/test/other/autoload_macro_test.rb +18 -0
  42. data/test/other/helpers_test.rb +58 -0
  43. data/test/other/private_helpers_test.rb +1 -1
  44. data/test/other/should_test.rb +16 -16
  45. data/test/rails_root/app/controllers/posts_controller.rb +6 -5
  46. data/test/rails_root/app/models/pets/dog.rb +10 -0
  47. data/test/rails_root/app/models/treat.rb +3 -0
  48. data/test/rails_root/app/models/user.rb +2 -2
  49. data/test/rails_root/app/views/layouts/posts.rhtml +2 -0
  50. data/test/rails_root/config/database.yml +1 -1
  51. data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  52. data/test/rails_root/db/migrate/001_create_users.rb +3 -2
  53. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  54. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  55. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  56. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  57. data/test/test_helper.rb +3 -1
  58. data/test/unit/address_test.rb +1 -1
  59. data/test/unit/dog_test.rb +5 -2
  60. data/test/unit/post_test.rb +7 -3
  61. data/test/unit/product_test.rb +2 -2
  62. data/test/unit/tag_test.rb +2 -1
  63. data/test/unit/user_test.rb +17 -8
  64. metadata +54 -4
  65. data/lib/shoulda/controller.rb +0 -30
  66. data/lib/shoulda/controller/formats/html.rb +0 -201
  67. data/lib/shoulda/controller/formats/xml.rb +0 -170
  68. data/lib/shoulda/controller/helpers.rb +0 -64
  69. data/lib/shoulda/controller/macros.rb +0 -316
  70. data/lib/shoulda/controller/resource_options.rb +0 -236
  71. data/test/rails_root/app/models/dog.rb +0 -5
data/README.rdoc CHANGED
@@ -7,14 +7,15 @@ Helpers:: #context and #should give you rSpec like test blocks.
7
7
  Macros:: Generate hundreds of lines of Controller and ActiveRecord tests with these powerful macros.
8
8
  They get you started quickly, and can help you ensure that your application is conforming to best practices.
9
9
  Assertions:: Many common rails testing idioms have been distilled into a set of useful assertions.
10
+ Matchers:: Rspec-compatible matchers providing the same tests as Shoulda macros.
10
11
 
11
12
  = Usage
12
13
 
13
- === Context Helpers (ThoughtBot::Shoulda::Context)
14
+ === Context Helpers (Shoulda::Context)
14
15
 
15
16
  Stop killing your fingers with all of those underscores... Name your tests with plain sentences!
16
17
 
17
- class UserTest << Test::Unit::TestCase
18
+ class UserTest < Test::Unit::TestCase
18
19
  context "A User instance" do
19
20
  setup do
20
21
  @user = User.find(:first)
@@ -43,7 +44,7 @@ Produces the following test methods:
43
44
 
44
45
  So readable!
45
46
 
46
- === ActiveRecord Tests (ThoughtBot::Shoulda::ActiveRecord::Macros)
47
+ === ActiveRecord Tests (Shoulda::ActiveRecord::Macros)
47
48
 
48
49
  Quick macro tests for your ActiveRecord associations and validations:
49
50
 
@@ -53,10 +54,10 @@ Quick macro tests for your ActiveRecord associations and validations:
53
54
  should_belong_to :user
54
55
  should_have_many :tags, :through => :taggings
55
56
 
56
- should_require_unique_attributes :title
57
- should_require_attributes :body, :message => /wtf/
58
- should_require_attributes :title
59
- should_only_allow_numeric_values_for :user_id
57
+ should_validate_uniqueness_of :title
58
+ should_validate_presence_of :body, :message => /wtf/
59
+ should_validate_presence_of :title
60
+ should_validate_numericality_of :user_id
60
61
  end
61
62
 
62
63
  class UserTest < Test::Unit::TestCase
@@ -66,12 +67,12 @@ Quick macro tests for your ActiveRecord associations and validations:
66
67
  should_allow_values_for :email, "a@b.com", "asdf@asdf.com"
67
68
  should_ensure_length_in_range :email, 1..100
68
69
  should_ensure_value_in_range :age, 1..100
69
- should_protect_attributes :password
70
+ should_not_allow_mass_assignment_of :password
70
71
  end
71
72
 
72
73
  Makes TDD so much easier.
73
74
 
74
- === Controller Tests (ThoughtBot::Shoulda::Controller::Macros)
75
+ === Controller Tests (Shoulda::Controller::Macros)
75
76
 
76
77
  Macros to test the most common controller patterns...
77
78
 
@@ -90,7 +91,7 @@ Macros to test the most common controller patterns...
90
91
  end
91
92
  end
92
93
 
93
- === Helpful Assertions (ThoughtBot::Shoulda::Assertions)
94
+ === Helpful Assertions (Shoulda::Assertions)
94
95
 
95
96
  More to come here, but have fun with what's there.
96
97
 
@@ -110,6 +111,33 @@ Any *.rb file under RAILS_ROOT/test/shoulda_macros/ or vendor/(plugins|gems)/gem
110
111
  end
111
112
  end
112
113
 
114
+ = Rails Installation
115
+
116
+ === As a Gem
117
+
118
+ Use this if you prefer to use versioned releases of shoulda. Specify the gem dependency in your config/environment.rb file:
119
+
120
+ Rails::Initializer.run do |config|
121
+ config.gem "thoughtbot-shoulda", :lib => "shoulda", :source => "http://gems.github.com"
122
+ end
123
+
124
+ Then:
125
+
126
+ $ rake gems:install
127
+ $ rake gems:unpack
128
+
129
+ === As a Plugin
130
+
131
+ Use this if you prefer to use the edge version of shoulda:
132
+
133
+ $ script/plugin install git://github.com/thoughtbot/shoulda.git
134
+
135
+ === As a Plugin (using git submodules)
136
+
137
+ Use this if you prefer the idea of being able to easily switch between using edge or a tagged version of shoulda:
138
+
139
+ $ git submodule add git://github.com/thoughtbot/shoulda.git vendor/plugins/shoulda
140
+
113
141
  = Credits
114
142
 
115
143
  Shoulda is maintained by {Tammer Saleh}[mailto:tsaleh@thoughtbot.com], and is funded by Thoughtbot[http://www.thoughtbot.com], inc.
data/Rakefile CHANGED
@@ -2,11 +2,13 @@ require 'rake'
2
2
  require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
4
  require 'rake/gempackagetask'
5
- require 'lib/shoulda/context'
5
+
6
+ $LOAD_PATH.unshift("lib")
7
+ require 'shoulda'
6
8
  load 'tasks/shoulda.rake'
7
9
 
8
10
  # Test::Unit::UI::VERBOSE
9
- test_files_pattern = 'test/{unit,functional,other}/**/*_test.rb'
11
+ test_files_pattern = 'test/{unit,functional,other,matchers}/**/*_test.rb'
10
12
  Rake::TestTask.new do |t|
11
13
  t.libs << 'lib'
12
14
  t.pattern = test_files_pattern
@@ -38,7 +40,7 @@ task :default => ['test']
38
40
 
39
41
  spec = Gem::Specification.new do |s|
40
42
  s.name = "shoulda"
41
- s.version = Thoughtbot::Shoulda::VERSION
43
+ s.version = Shoulda::VERSION
42
44
  s.summary = "Making tests easy on the fingers and eyes"
43
45
  s.homepage = "http://thoughtbot.com/projects/shoulda"
44
46
  s.rubyforge_project = "shoulda"
data/lib/shoulda.rb CHANGED
@@ -1,17 +1,9 @@
1
- require 'shoulda/context'
2
- require 'shoulda/proc_extensions'
3
- require 'shoulda/assertions'
4
- require 'shoulda/macros'
5
- require 'shoulda/helpers'
6
- require 'shoulda/rails' if defined? RAILS_ROOT
1
+ module Shoulda
2
+ VERSION = "2.9.1"
3
+ end
7
4
 
8
- module Test # :nodoc: all
9
- module Unit
10
- class TestCase
11
- extend Thoughtbot::Shoulda
12
- include ThoughtBot::Shoulda::Assertions
13
- extend ThoughtBot::Shoulda::Macros
14
- include ThoughtBot::Shoulda::Helpers
15
- end
16
- end
5
+ if defined? Spec
6
+ require 'shoulda/rspec'
7
+ else
8
+ require 'shoulda/test_unit'
17
9
  end
@@ -4,7 +4,7 @@ require 'shoulda/action_mailer/assertions'
4
4
  module Test # :nodoc: all
5
5
  module Unit
6
6
  class TestCase
7
- include ThoughtBot::Shoulda::ActionMailer::Assertions
7
+ include Shoulda::ActionMailer::Assertions
8
8
  end
9
9
  end
10
10
  end
@@ -1,39 +1,38 @@
1
- module ThoughtBot # :nodoc:
2
- module Shoulda # :nodoc:
3
- module ActionMailer # :nodoc:
4
- module Assertions
5
- # Asserts that an email was delivered. Can take a block that can further
6
- # narrow down the types of emails you're expecting.
7
- #
8
- # assert_sent_email
9
- #
10
- # Passes if ActionMailer::Base.deliveries has an email
11
- #
12
- # assert_sent_email do |email|
13
- # email.subject =~ /hi there/ && email.to.include?('none@none.com')
14
- # end
15
- #
16
- # Passes if there is an email with subject containing 'hi there' and
17
- # 'none@none.com' as one of the recipients.
18
- #
19
- def assert_sent_email
20
- emails = ::ActionMailer::Base.deliveries
21
- assert !emails.empty?, "No emails were sent"
22
- if block_given?
23
- matching_emails = emails.select {|email| yield email }
24
- assert !matching_emails.empty?, "None of the emails matched."
25
- end
1
+ module Shoulda # :nodoc:
2
+ module ActionMailer # :nodoc:
3
+ module Assertions
4
+ # Asserts that an email was delivered. Can take a block that can further
5
+ # narrow down the types of emails you're expecting.
6
+ #
7
+ # assert_sent_email
8
+ #
9
+ # Passes if ActionMailer::Base.deliveries has an email
10
+ #
11
+ # assert_sent_email do |email|
12
+ # email.subject =~ /hi there/ && email.to.include?('none@none.com')
13
+ # end
14
+ #
15
+ # Passes if there is an email with subject containing 'hi there' and
16
+ # 'none@none.com' as one of the recipients.
17
+ #
18
+ def assert_sent_email
19
+ emails = ::ActionMailer::Base.deliveries
20
+ assert !emails.empty?, "No emails were sent"
21
+ if block_given?
22
+ matching_emails = emails.select {|email| yield email }
23
+ assert !matching_emails.empty?, "None of the emails matched."
26
24
  end
25
+ end
27
26
 
28
- # Asserts that no ActionMailer mails were delivered
29
- #
30
- # assert_did_not_send_email
31
- def assert_did_not_send_email
32
- msg = "Sent #{::ActionMailer::Base.deliveries.size} emails.\n"
33
- ::ActionMailer::Base.deliveries.each { |m| msg << " '#{m.subject}' sent to #{m.to.to_sentence}\n" }
34
- assert ::ActionMailer::Base.deliveries.empty?, msg
35
- end
27
+ # Asserts that no ActionMailer mails were delivered
28
+ #
29
+ # assert_did_not_send_email
30
+ def assert_did_not_send_email
31
+ msg = "Sent #{::ActionMailer::Base.deliveries.size} emails.\n"
32
+ ::ActionMailer::Base.deliveries.each { |m| msg << " '#{m.subject}' sent to #{m.to.to_sentence}\n" }
33
+ assert ::ActionMailer::Base.deliveries.empty?, msg
36
34
  end
37
35
  end
38
36
  end
39
37
  end
38
+
@@ -1,12 +1,16 @@
1
1
  require 'shoulda'
2
+ require 'shoulda/active_record/helpers'
3
+ require 'shoulda/active_record/matchers'
2
4
  require 'shoulda/active_record/assertions'
3
5
  require 'shoulda/active_record/macros'
4
6
 
5
7
  module Test # :nodoc: all
6
8
  module Unit
7
9
  class TestCase
8
- include ThoughtBot::Shoulda::ActiveRecord::Assertions
9
- extend ThoughtBot::Shoulda::ActiveRecord::Macros
10
+ include Shoulda::ActiveRecord::Helpers
11
+ include Shoulda::ActiveRecord::Matchers
12
+ include Shoulda::ActiveRecord::Assertions
13
+ extend Shoulda::ActiveRecord::Macros
10
14
  end
11
15
  end
12
16
  end
@@ -1,87 +1,68 @@
1
- module ThoughtBot # :nodoc:
2
- module Shoulda # :nodoc:
3
- module ActiveRecord # :nodoc:
4
- module Assertions
5
- # Asserts that the given object can be saved
6
- #
7
- # assert_save User.new(params)
8
- def assert_save(obj)
9
- assert obj.save, "Errors: #{pretty_error_messages obj}"
10
- obj.reload
11
- end
12
-
13
- # Asserts that the given object is valid
14
- #
15
- # assert_valid User.new(params)
16
- def assert_valid(obj)
17
- assert obj.valid?, "Errors: #{pretty_error_messages obj}"
18
- end
19
-
20
- # Asserts that an Active Record model validates with the passed
21
- # <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
22
- # contained within the list of errors for that attribute.
23
- #
24
- # assert_good_value(User.new, :email, "user@example.com")
25
- # assert_good_value(User.new, :ssn, "123456789", /length/)
26
- #
27
- # If a class is passed as the first argument, a new object will be
28
- # instantiated before the assertion. If an instance variable exists with
29
- # the same name as the class (underscored), that object will be used
30
- # instead.
31
- #
32
- # assert_good_value(User, :email, "user@example.com")
33
- #
34
- # @product = Product.new(:tangible => false)
35
- # assert_good_value(Product, :price, "0")
36
- def assert_good_value(object_or_klass, attribute, value, error_message_to_avoid = //)
37
- object = get_instance_of(object_or_klass)
38
- object.send("#{attribute}=", value)
39
- object.valid?
40
- assert_does_not_contain(object.errors.on(attribute), error_message_to_avoid, "when set to #{value.inspect}")
41
- end
42
-
43
- # Asserts that an Active Record model invalidates the passed
44
- # <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
45
- # contained within the list of errors for that attribute.
46
- #
47
- # assert_bad_value(User.new, :email, "invalid")
48
- # assert_bad_value(User.new, :ssn, "123", /length/)
49
- #
50
- # If a class is passed as the first argument, a new object will be
51
- # instantiated before the assertion. If an instance variable exists with
52
- # the same name as the class (underscored), that object will be used
53
- # instead.
54
- #
55
- # assert_bad_value(User, :email, "invalid")
56
- #
57
- # @product = Product.new(:tangible => true)
58
- # assert_bad_value(Product, :price, "0")
59
- def assert_bad_value(object_or_klass, attribute, value,
60
- error_message_to_expect = self.class.default_error_message(:invalid))
61
- object = get_instance_of(object_or_klass)
62
- object.send("#{attribute}=", value)
63
- assert !object.valid?, "#{object.class} allowed #{value.inspect} as a value for #{attribute}"
64
- assert object.errors.on(attribute), "There are no errors on #{attribute} after being set to #{value.inspect}"
65
- assert_contains(object.errors.on(attribute), error_message_to_expect, "when set to #{value.inspect}")
66
- end
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Assertions
4
+ # Asserts that the given object can be saved
5
+ #
6
+ # assert_save User.new(params)
7
+ def assert_save(obj)
8
+ assert obj.save, "Errors: #{pretty_error_messages obj}"
9
+ obj.reload
10
+ end
67
11
 
68
- def pretty_error_messages(obj)
69
- obj.errors.map do |a, m|
70
- msg = "#{a} #{m}"
71
- msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
72
- end
73
- end
12
+ # Asserts that the given object is valid
13
+ #
14
+ # assert_valid User.new(params)
15
+ def assert_valid(obj)
16
+ assert obj.valid?, "Errors: #{pretty_error_messages obj}"
17
+ end
74
18
 
75
- private
19
+ # Asserts that an Active Record model validates with the passed
20
+ # <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
21
+ # contained within the list of errors for that attribute.
22
+ #
23
+ # assert_good_value(User.new, :email, "user@example.com")
24
+ # assert_good_value(User.new, :ssn, "123456789", /length/)
25
+ #
26
+ # If a class is passed as the first argument, a new object will be
27
+ # instantiated before the assertion. If an instance variable exists with
28
+ # the same name as the class (underscored), that object will be used
29
+ # instead.
30
+ #
31
+ # assert_good_value(User, :email, "user@example.com")
32
+ #
33
+ # @product = Product.new(:tangible => false)
34
+ # assert_good_value(Product, :price, "0")
35
+ def assert_good_value(object_or_klass, attribute, value, error_message_to_avoid = nil)
36
+ object = get_instance_of(object_or_klass)
37
+ matcher = allow_value(value).
38
+ for(attribute).
39
+ with_message(error_message_to_avoid)
40
+ assert_accepts(matcher, object)
41
+ end
76
42
 
77
- def get_instance_of(object_or_klass)
78
- if object_or_klass.is_a?(Class)
79
- klass = object_or_klass
80
- instance_variable_get("@#{klass.to_s.underscore}") || klass.new
81
- else
82
- object_or_klass
83
- end
84
- end
43
+ # Asserts that an Active Record model invalidates the passed
44
+ # <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
45
+ # contained within the list of errors for that attribute.
46
+ #
47
+ # assert_bad_value(User.new, :email, "invalid")
48
+ # assert_bad_value(User.new, :ssn, "123", /length/)
49
+ #
50
+ # If a class is passed as the first argument, a new object will be
51
+ # instantiated before the assertion. If an instance variable exists with
52
+ # the same name as the class (underscored), that object will be used
53
+ # instead.
54
+ #
55
+ # assert_bad_value(User, :email, "invalid")
56
+ #
57
+ # @product = Product.new(:tangible => true)
58
+ # assert_bad_value(Product, :price, "0")
59
+ def assert_bad_value(object_or_klass, attribute, value,
60
+ error_message_to_expect = nil)
61
+ object = get_instance_of(object_or_klass)
62
+ matcher = allow_value(value).
63
+ for(attribute).
64
+ with_message(error_message_to_expect)
65
+ assert_rejects(matcher, object)
85
66
  end
86
67
  end
87
68
  end
@@ -0,0 +1,40 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Helpers
4
+ def pretty_error_messages(obj) # :nodoc:
5
+ obj.errors.map do |a, m|
6
+ msg = "#{a} #{m}"
7
+ msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
8
+ end
9
+ end
10
+
11
+ def get_instance_of(object_or_klass)
12
+ if object_or_klass.is_a?(Class)
13
+ klass = object_or_klass
14
+ instance_variable_get("@#{instance_variable_name_for(klass)}") || klass.new
15
+ else
16
+ object_or_klass
17
+ end
18
+ end
19
+
20
+ def instance_variable_name_for(klass)
21
+ klass.to_s.split('::').last.underscore
22
+ end
23
+
24
+ # Helper method that determines the default error message used by Active
25
+ # Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
26
+ # introduced I18n module used for localization.
27
+ #
28
+ # default_error_message(:blank)
29
+ # default_error_message(:too_short, :count => 5)
30
+ # default_error_message(:too_long, :count => 60)
31
+ def default_error_message(key, values = {})
32
+ if Object.const_defined?(:I18n) # Rails >= 2.2
33
+ I18n.translate("activerecord.errors.messages.#{key}", values)
34
+ else # Rails <= 2.1.x
35
+ ::ActiveRecord::Errors.default_error_messages[key] % values[:count]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,713 +1,584 @@
1
- module ThoughtBot # :nodoc:
2
- module Shoulda # :nodoc:
3
- module ActiveRecord # :nodoc:
4
- module MacroHelpers # :nodoc:
5
- # Helper method that determines the default error message used by Active
6
- # Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
7
- # introduced I18n module used for localization.
8
- #
9
- # default_error_message(:blank)
10
- # default_error_message(:too_short, :count => 5)
11
- # default_error_message(:too_long, :count => 60)
12
- def default_error_message(key, values = {})
13
- if Object.const_defined?(:I18n) # Rails >= 2.2
14
- I18n.translate("activerecord.errors.messages.#{key}", values)
15
- else # Rails <= 2.1.x
16
- ::ActiveRecord::Errors.default_error_messages[key] % values[:count]
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ # = Macro test helpers for your active record models
4
+ #
5
+ # These helpers will test most of the validations and associations for your ActiveRecord models.
6
+ #
7
+ # class UserTest < Test::Unit::TestCase
8
+ # should_validate_presence_of :name, :phone_number
9
+ # should_not_allow_values_for :phone_number, "abcd", "1234"
10
+ # should_allow_values_for :phone_number, "(123) 456-7890"
11
+ #
12
+ # should_not_allow_mass_assignment_of :password
13
+ #
14
+ # should_have_one :profile
15
+ # should_have_many :dogs
16
+ # should_have_many :messes, :through => :dogs
17
+ # should_belong_to :lover
18
+ # end
19
+ #
20
+ # For all of these helpers, the last parameter may be a hash of options.
21
+ #
22
+ module Macros
23
+ include Helpers
24
+ include Matchers
25
+
26
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
27
+ #
28
+ # If an instance variable has been created in the setup named after the
29
+ # model being tested, then this method will use that. Otherwise, it will
30
+ # create a new instance to test against.
31
+ #
32
+ # Options:
33
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
34
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
35
+ #
36
+ # Example:
37
+ # should_validate_presence_of :name, :phone_number
38
+ #
39
+ def should_validate_presence_of(*attributes)
40
+ message = get_options!(attributes, :message)
41
+ klass = model_class
42
+
43
+ attributes.each do |attribute|
44
+ matcher = validate_presence_of(attribute).with_message(message)
45
+ should matcher.description do
46
+ assert_accepts(matcher, get_instance_of(klass))
17
47
  end
18
48
  end
19
49
  end
50
+
51
+ # Deprecated. See should_validate_presence_of
52
+ def should_require_attributes(*attributes)
53
+ warn "[DEPRECATION] should_require_attributes is deprecated. " <<
54
+ "Use should_validate_presence_of instead."
55
+ should_validate_presence_of(*attributes)
56
+ end
20
57
 
21
- # = Macro test helpers for your active record models
22
- #
23
- # These helpers will test most of the validations and associations for your ActiveRecord models.
24
- #
25
- # class UserTest < Test::Unit::TestCase
26
- # should_require_attributes :name, :phone_number
27
- # should_not_allow_values_for :phone_number, "abcd", "1234"
28
- # should_allow_values_for :phone_number, "(123) 456-7890"
29
- #
30
- # should_protect_attributes :password
58
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
59
+ # Requires an existing record
31
60
  #
32
- # should_have_one :profile
33
- # should_have_many :dogs
34
- # should_have_many :messes, :through => :dogs
35
- # should_belong_to :lover
36
- # end
61
+ # Options:
62
+
63
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
64
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
65
+ # * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
66
+ # * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
67
+ # exact match. Ignored by non-text attributes. Default = <tt>true</tt>
37
68
  #
38
- # For all of these helpers, the last parameter may be a hash of options.
69
+ # Examples:
70
+ # should_validate_uniqueness_of :keyword, :username
71
+ # should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
72
+ # should_validate_uniqueness_of :email, :scoped_to => :name
73
+ # should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
74
+ # should_validate_uniqueness_of :email, :case_sensitive => false
39
75
  #
40
- module Macros
41
- include MacroHelpers
76
+ def should_validate_uniqueness_of(*attributes)
77
+ message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
78
+ scope = [*scope].compact
79
+ case_sensitive = true if case_sensitive.nil?
42
80
 
43
- # <b>DEPRECATED:</b> Use <tt>fixtures :all</tt> instead
44
- #
45
- # Loads all fixture files (<tt>test/fixtures/*.yml</tt>)
46
- def load_all_fixtures
47
- warn "[DEPRECATION] load_all_fixtures is deprecated. Use `fixtures :all` instead."
48
- fixtures :all
49
- end
81
+ klass = model_class
50
82
 
51
- # Ensures that the model cannot be saved if one of the attributes listed is not present.
52
- #
53
- # If an instance variable has been created in the setup named after the
54
- # model being tested, then this method will use that. Otherwise, it will
55
- # create a new instance to test against.
56
- #
57
- # Options:
58
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
59
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
60
- #
61
- # Example:
62
- # should_require_attributes :name, :phone_number
63
- #
64
- def should_require_attributes(*attributes)
65
- message = get_options!(attributes, :message)
66
- message ||= default_error_message(:blank)
67
- klass = model_class
68
-
69
- attributes.each do |attribute|
70
- should "require #{attribute} to be set" do
71
- assert_bad_value(klass, attribute, nil, message)
72
- end
83
+ attributes.each do |attribute|
84
+ matcher = validate_uniqueness_of(attribute).
85
+ with_message(message).scoped_to(scope)
86
+ matcher = matcher.case_insensitive unless case_sensitive
87
+ should matcher.description do
88
+ assert_accepts(matcher, get_instance_of(klass))
73
89
  end
74
90
  end
91
+ end
75
92
 
76
- # Ensures that the model cannot be saved if one of the attributes listed is not unique.
77
- # Requires an existing record
78
- #
79
- # Options:
80
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
81
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
82
- # * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
83
- #
84
- # Examples:
85
- # should_require_unique_attributes :keyword, :username
86
- # should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
87
- # should_require_unique_attributes :email, :scoped_to => :name
88
- # should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
89
- #
90
- def should_require_unique_attributes(*attributes)
91
- message, scope = get_options!(attributes, :message, :scoped_to)
92
- scope = [*scope].compact
93
- message ||= default_error_message(:taken)
94
-
95
- klass = model_class
96
- attributes.each do |attribute|
97
- attribute = attribute.to_sym
98
- should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" unless scope.blank?}" do
99
- assert existing = klass.find(:first), "Can't find first #{klass}"
100
- object = klass.new
101
- existing_value = existing.send(attribute)
102
-
103
- if !scope.blank?
104
- scope.each do |s|
105
- assert_respond_to object, :"#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute."
106
- object.send("#{s}=", existing.send(s))
107
- end
108
- end
109
- assert_bad_value(object, attribute, existing_value, message)
110
-
111
- # Now test that the object is valid when changing the scoped attribute
112
- # TODO: There is a chance that we could change the scoped field
113
- # to a value that's already taken. An alternative implementation
114
- # could actually find all values for scope and create a unique
115
- # one.
116
- if !scope.blank?
117
- scope.each do |s|
118
- # Assume the scope is a foreign key if the field is nil
119
- object.send("#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next)
120
- assert_good_value(object, attribute, existing_value, message)
121
- end
122
- end
123
- end
124
- end
125
- end
93
+ # Deprecated. See should_validate_uniqueness_of
94
+ def should_require_unique_attributes(*attributes)
95
+ warn "[DEPRECATION] should_require_unique_attributes is deprecated. " <<
96
+ "Use should_validate_uniqueness_of instead."
97
+ should_validate_uniqueness_of(*attributes)
98
+ end
126
99
 
127
- # Ensures that the attribute cannot be set on mass update.
128
- #
129
- # should_protect_attributes :password, :admin_flag
130
- #
131
- def should_protect_attributes(*attributes)
132
- get_options!(attributes)
133
- klass = model_class
134
-
135
- attributes.each do |attribute|
136
- attribute = attribute.to_sym
137
- should "protect #{attribute} from mass updates" do
138
- protected = klass.protected_attributes || []
139
- accessible = klass.accessible_attributes || []
140
-
141
- assert protected.include?(attribute.to_s) ||
142
- (!accessible.empty? && !accessible.include?(attribute.to_s)),
143
- (accessible.empty? ?
144
- "#{klass} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." :
145
- "#{klass} has made #{attribute} accessible")
146
- end
147
- end
148
- end
100
+ # Ensures that the attribute can be set on mass update.
101
+ #
102
+ # should_allow_mass_assignment_of :first_name, :last_name
103
+ #
104
+ def should_allow_mass_assignment_of(*attributes)
105
+ get_options!(attributes)
106
+ klass = model_class
149
107
 
150
- # Ensures that the attribute cannot be changed once the record has been created.
151
- #
152
- # should_have_readonly_attributes :password, :admin_flag
153
- #
154
- def should_have_readonly_attributes(*attributes)
155
- get_options!(attributes)
156
- klass = model_class
157
-
158
- attributes.each do |attribute|
159
- attribute = attribute.to_sym
160
- should "make #{attribute} read-only" do
161
- readonly = klass.readonly_attributes || []
162
-
163
- assert readonly.include?(attribute.to_s),
164
- (readonly.empty? ?
165
- "#{klass} attribute #{attribute} is not read-only" :
166
- "#{klass} is making #{readonly.to_a.to_sentence} read-only, but not #{attribute}.")
167
- end
108
+ attributes.each do |attribute|
109
+ matcher = allow_mass_assignment_of(attribute)
110
+ should matcher.description do
111
+ assert_accepts matcher, klass.new
168
112
  end
169
113
  end
114
+ end
170
115
 
171
- # Ensures that the attribute cannot be set to the given values
172
- #
173
- # If an instance variable has been created in the setup named after the
174
- # model being tested, then this method will use that. Otherwise, it will
175
- # create a new instance to test against.
176
- #
177
- # Options:
178
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
179
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
180
- #
181
- # Example:
182
- # should_not_allow_values_for :isbn, "bad 1", "bad 2"
183
- #
184
- def should_not_allow_values_for(attribute, *bad_values)
185
- message = get_options!(bad_values, :message)
186
- message ||= default_error_message(:invalid)
187
- klass = model_class
188
- bad_values.each do |v|
189
- should "not allow #{attribute} to be set to #{v.inspect}" do
190
- assert_bad_value(klass, attribute, v, message)
191
- end
192
- end
193
- end
116
+ # Ensures that the attribute cannot be set on mass update.
117
+ #
118
+ # should_not_allow_mass_assignment_of :password, :admin_flag
119
+ #
120
+ def should_not_allow_mass_assignment_of(*attributes)
121
+ get_options!(attributes)
122
+ klass = model_class
194
123
 
195
- # Ensures that the attribute can be set to the given values.
196
- #
197
- # If an instance variable has been created in the setup named after the
198
- # model being tested, then this method will use that. Otherwise, it will
199
- # create a new instance to test against.
200
- #
201
- # Example:
202
- # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
203
- #
204
- def should_allow_values_for(attribute, *good_values)
205
- get_options!(good_values)
206
- klass = model_class
207
- good_values.each do |v|
208
- should "allow #{attribute} to be set to #{v.inspect}" do
209
- assert_good_value(klass, attribute, v)
210
- end
124
+ attributes.each do |attribute|
125
+ matcher = allow_mass_assignment_of(attribute)
126
+ should "not #{matcher.description}" do
127
+ assert_rejects matcher, klass.new
211
128
  end
212
129
  end
130
+ end
213
131
 
214
- # Ensures that the length of the attribute is in the given range
215
- #
216
- # If an instance variable has been created in the setup named after the
217
- # model being tested, then this method will use that. Otherwise, it will
218
- # create a new instance to test against.
219
- #
220
- # Options:
221
- # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
222
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
223
- # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
224
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
225
- #
226
- # Example:
227
- # should_ensure_length_in_range :password, (6..20)
228
- #
229
- def should_ensure_length_in_range(attribute, range, opts = {})
230
- short_message, long_message = get_options!([opts], :short_message, :long_message)
231
- short_message ||= default_error_message(:too_short, :count => range.first)
232
- long_message ||= default_error_message(:too_long, :count => range.last)
233
-
234
- klass = model_class
235
- min_length = range.first
236
- max_length = range.last
237
- same_length = (min_length == max_length)
238
-
239
- if min_length > 0
240
- should "not allow #{attribute} to be less than #{min_length} chars long" do
241
- min_value = "x" * (min_length - 1)
242
- assert_bad_value(klass, attribute, min_value, short_message)
243
- end
244
- end
245
-
246
- if min_length > 0
247
- should "allow #{attribute} to be exactly #{min_length} chars long" do
248
- min_value = "x" * min_length
249
- assert_good_value(klass, attribute, min_value, short_message)
250
- end
251
- end
132
+ # Deprecated. See should_not_allow_mass_assignment_of
133
+ def should_protect_attributes(*attributes)
134
+ warn "[DEPRECATION] should_protect_attributes is deprecated. " <<
135
+ "Use should_not_allow_mass_assignment_of instead."
136
+ should_not_allow_mass_assignment_of(*attributes)
137
+ end
252
138
 
253
- should "not allow #{attribute} to be more than #{max_length} chars long" do
254
- max_value = "x" * (max_length + 1)
255
- assert_bad_value(klass, attribute, max_value, long_message)
256
- end
139
+ # Ensures that the attribute cannot be changed once the record has been created.
140
+ #
141
+ # should_have_readonly_attributes :password, :admin_flag
142
+ #
143
+ def should_have_readonly_attributes(*attributes)
144
+ get_options!(attributes)
145
+ klass = model_class
257
146
 
258
- unless same_length
259
- should "allow #{attribute} to be exactly #{max_length} chars long" do
260
- max_value = "x" * max_length
261
- assert_good_value(klass, attribute, max_value, long_message)
262
- end
147
+ attributes.each do |attribute|
148
+ matcher = have_readonly_attribute(attribute)
149
+ should matcher.description do
150
+ assert_accepts matcher, klass.new
263
151
  end
264
152
  end
153
+ end
265
154
 
266
- # Ensures that the length of the attribute is at least a certain length
267
- #
268
- # If an instance variable has been created in the setup named after the
269
- # model being tested, then this method will use that. Otherwise, it will
270
- # create a new instance to test against.
271
- #
272
- # Options:
273
- # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
274
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
275
- #
276
- # Example:
277
- # should_ensure_length_at_least :name, 3
278
- #
279
- def should_ensure_length_at_least(attribute, min_length, opts = {})
280
- short_message = get_options!([opts], :short_message)
281
- short_message ||= default_error_message(:too_short, :count => min_length)
282
-
283
- klass = model_class
284
-
285
- if min_length > 0
286
- min_value = "x" * (min_length - 1)
287
- should "not allow #{attribute} to be less than #{min_length} chars long" do
288
- assert_bad_value(klass, attribute, min_value, short_message)
289
- end
290
- end
291
- should "allow #{attribute} to be at least #{min_length} chars long" do
292
- valid_value = "x" * (min_length)
293
- assert_good_value(klass, attribute, valid_value, short_message)
155
+ # Ensures that the attribute cannot be set to the given values
156
+ #
157
+ # If an instance variable has been created in the setup named after the
158
+ # model being tested, then this method will use that. Otherwise, it will
159
+ # create a new instance to test against.
160
+ #
161
+ # Options:
162
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
163
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
164
+ #
165
+ # Example:
166
+ # should_not_allow_values_for :isbn, "bad 1", "bad 2"
167
+ #
168
+ def should_not_allow_values_for(attribute, *bad_values)
169
+ message = get_options!(bad_values, :message)
170
+ klass = model_class
171
+ bad_values.each do |value|
172
+ matcher = allow_value(value).for(attribute).with_message(message)
173
+ should "not #{matcher.description}" do
174
+ assert_rejects matcher, get_instance_of(klass)
294
175
  end
295
176
  end
177
+ end
296
178
 
297
- # Ensures that the length of the attribute is exactly a certain length
298
- #
299
- # If an instance variable has been created in the setup named after the
300
- # model being tested, then this method will use that. Otherwise, it will
301
- # create a new instance to test against.
302
- #
303
- # Options:
304
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
305
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
306
- #
307
- # Example:
308
- # should_ensure_length_is :ssn, 9
309
- #
310
- def should_ensure_length_is(attribute, length, opts = {})
311
- message = get_options!([opts], :message)
312
- message ||= default_error_message(:wrong_length, :count => length)
313
-
314
- klass = model_class
315
-
316
- should "not allow #{attribute} to be less than #{length} chars long" do
317
- min_value = "x" * (length - 1)
318
- assert_bad_value(klass, attribute, min_value, message)
319
- end
320
-
321
- should "not allow #{attribute} to be greater than #{length} chars long" do
322
- max_value = "x" * (length + 1)
323
- assert_bad_value(klass, attribute, max_value, message)
179
+ # Ensures that the attribute can be set to the given values.
180
+ #
181
+ # If an instance variable has been created in the setup named after the
182
+ # model being tested, then this method will use that. Otherwise, it will
183
+ # create a new instance to test against.
184
+ #
185
+ # Example:
186
+ # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
187
+ #
188
+ def should_allow_values_for(attribute, *good_values)
189
+ get_options!(good_values)
190
+ klass = model_class
191
+ klass = model_class
192
+ good_values.each do |value|
193
+ matcher = allow_value(value).for(attribute)
194
+ should matcher.description do
195
+ assert_accepts matcher, get_instance_of(klass)
324
196
  end
197
+ end
198
+ end
325
199
 
326
- should "allow #{attribute} to be #{length} chars long" do
327
- valid_value = "x" * (length)
328
- assert_good_value(klass, attribute, valid_value, message)
329
- end
200
+ # Ensures that the length of the attribute is in the given range
201
+ #
202
+ # If an instance variable has been created in the setup named after the
203
+ # model being tested, then this method will use that. Otherwise, it will
204
+ # create a new instance to test against.
205
+ #
206
+ # Options:
207
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
208
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
209
+ # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
210
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
211
+ #
212
+ # Example:
213
+ # should_ensure_length_in_range :password, (6..20)
214
+ #
215
+ def should_ensure_length_in_range(attribute, range, opts = {})
216
+ short_message, long_message = get_options!([opts],
217
+ :short_message,
218
+ :long_message)
219
+ klass = model_class
220
+
221
+ matcher = ensure_length_of(attribute).
222
+ is_at_least(range.first).
223
+ with_short_message(short_message).
224
+ is_at_most(range.last).
225
+ with_long_message(long_message)
226
+
227
+ should matcher.description do
228
+ assert_accepts matcher, get_instance_of(klass)
330
229
  end
230
+ end
331
231
 
332
- # Ensure that the attribute is in the range specified
333
- #
334
- # If an instance variable has been created in the setup named after the
335
- # model being tested, then this method will use that. Otherwise, it will
336
- # create a new instance to test against.
337
- #
338
- # Options:
339
- # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
340
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
341
- # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
342
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
343
- #
344
- # Example:
345
- # should_ensure_value_in_range :age, (0..100)
346
- #
347
- def should_ensure_value_in_range(attribute, range, opts = {})
348
- low_message, high_message = get_options!([opts], :low_message, :high_message)
349
- low_message ||= default_error_message(:inclusion)
350
- high_message ||= default_error_message(:inclusion)
351
-
352
- klass = model_class
353
- min = range.first
354
- max = range.last
355
-
356
- should "not allow #{attribute} to be less than #{min}" do
357
- v = min - 1
358
- assert_bad_value(klass, attribute, v, low_message)
359
- end
232
+ # Ensures that the length of the attribute is at least a certain length
233
+ #
234
+ # If an instance variable has been created in the setup named after the
235
+ # model being tested, then this method will use that. Otherwise, it will
236
+ # create a new instance to test against.
237
+ #
238
+ # Options:
239
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
240
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
241
+ #
242
+ # Example:
243
+ # should_ensure_length_at_least :name, 3
244
+ #
245
+ def should_ensure_length_at_least(attribute, min_length, opts = {})
246
+ short_message = get_options!([opts], :short_message)
247
+ klass = model_class
360
248
 
361
- should "allow #{attribute} to be #{min}" do
362
- v = min
363
- assert_good_value(klass, attribute, v, low_message)
364
- end
249
+ matcher = ensure_length_of(attribute).
250
+ is_at_least(min_length).
251
+ with_short_message(short_message)
365
252
 
366
- should "not allow #{attribute} to be more than #{max}" do
367
- v = max + 1
368
- assert_bad_value(klass, attribute, v, high_message)
369
- end
253
+ should matcher.description do
254
+ assert_accepts matcher, get_instance_of(klass)
255
+ end
256
+ end
370
257
 
371
- should "allow #{attribute} to be #{max}" do
372
- v = max
373
- assert_good_value(klass, attribute, v, high_message)
374
- end
258
+ # Ensures that the length of the attribute is exactly a certain length
259
+ #
260
+ # If an instance variable has been created in the setup named after the
261
+ # model being tested, then this method will use that. Otherwise, it will
262
+ # create a new instance to test against.
263
+ #
264
+ # Options:
265
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
266
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
267
+ #
268
+ # Example:
269
+ # should_ensure_length_is :ssn, 9
270
+ #
271
+ def should_ensure_length_is(attribute, length, opts = {})
272
+ message = get_options!([opts], :message)
273
+ klass = model_class
274
+ matcher = ensure_length_of(attribute).
275
+ is_equal_to(length).
276
+ with_message(message)
277
+
278
+ should matcher.description do
279
+ assert_accepts matcher, get_instance_of(klass)
375
280
  end
281
+ end
376
282
 
377
- # Ensure that the attribute is numeric
378
- #
379
- # If an instance variable has been created in the setup named after the
380
- # model being tested, then this method will use that. Otherwise, it will
381
- # create a new instance to test against.
382
- #
383
- # Options:
384
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
385
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
386
- #
387
- # Example:
388
- # should_only_allow_numeric_values_for :age
389
- #
390
- def should_only_allow_numeric_values_for(*attributes)
391
- message = get_options!(attributes, :message)
392
- message ||= default_error_message(:not_a_number)
393
- klass = model_class
394
- attributes.each do |attribute|
395
- attribute = attribute.to_sym
396
- should "only allow numeric values for #{attribute}" do
397
- assert_bad_value(klass, attribute, "abcd", message)
398
- end
399
- end
283
+ # Ensure that the attribute is in the range specified
284
+ #
285
+ # If an instance variable has been created in the setup named after the
286
+ # model being tested, then this method will use that. Otherwise, it will
287
+ # create a new instance to test against.
288
+ #
289
+ # Options:
290
+ # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
291
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
292
+ # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
293
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
294
+ #
295
+ # Example:
296
+ # should_ensure_value_in_range :age, (0..100)
297
+ #
298
+ def should_ensure_value_in_range(attribute, range, opts = {})
299
+ message = get_options!([opts], :message)
300
+ message ||= default_error_message(:inclusion)
301
+
302
+ klass = model_class
303
+ matcher = ensure_inclusion_of(attribute).
304
+ in_range(range).
305
+ with_message(message)
306
+ should matcher.description do
307
+ assert_accepts matcher, get_instance_of(klass)
400
308
  end
309
+ end
401
310
 
402
- # Ensures that the has_many relationship exists. Will also test that the
403
- # associated table has the required columns. Works with polymorphic
404
- # associations.
405
- #
406
- # Options:
407
- # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
408
- # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
409
- #
410
- # Example:
411
- # should_have_many :friends
412
- # should_have_many :enemies, :through => :friends
413
- # should_have_many :enemies, :dependent => :destroy
414
- #
415
- def should_have_many(*associations)
416
- through, dependent = get_options!(associations, :through, :dependent)
417
- klass = model_class
418
- associations.each do |association|
419
- name = "have many #{association}"
420
- name += " through #{through}" if through
421
- name += " dependent => #{dependent}" if dependent
422
- should name do
423
- reflection = klass.reflect_on_association(association)
424
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
425
- assert_equal :has_many, reflection.macro
426
-
427
- if through
428
- through_reflection = klass.reflect_on_association(through)
429
- assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
430
- assert_equal(through, reflection.options[:through])
431
- end
432
-
433
- if dependent
434
- assert_equal dependent.to_s,
435
- reflection.options[:dependent].to_s,
436
- "#{association} should have #{dependent} dependency"
437
- end
438
-
439
- # Check for the existence of the foreign key on the other table
440
- unless reflection.options[:through]
441
- if reflection.options[:foreign_key]
442
- fk = reflection.options[:foreign_key]
443
- elsif reflection.options[:as]
444
- fk = reflection.options[:as].to_s.foreign_key
445
- else
446
- fk = reflection.primary_key_name
447
- end
448
-
449
- associated_klass_name = (reflection.options[:class_name] || association.to_s.classify)
450
- associated_klass = associated_klass_name.constantize
451
-
452
- assert associated_klass.column_names.include?(fk.to_s),
453
- "#{associated_klass.name} does not have a #{fk} foreign key."
454
- end
455
- end
311
+ # Ensure that the attribute is numeric
312
+ #
313
+ # If an instance variable has been created in the setup named after the
314
+ # model being tested, then this method will use that. Otherwise, it will
315
+ # create a new instance to test against.
316
+ #
317
+ # Options:
318
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
319
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
320
+ #
321
+ # Example:
322
+ # should_validate_numericality_of :age
323
+ #
324
+ def should_validate_numericality_of(*attributes)
325
+ message = get_options!(attributes, :message)
326
+ klass = model_class
327
+ attributes.each do |attribute|
328
+ matcher = validate_numericality_of(attribute).
329
+ with_message(message)
330
+ should matcher.description do
331
+ assert_accepts matcher, get_instance_of(klass)
456
332
  end
457
333
  end
334
+ end
335
+
336
+ # Deprecated. See should_validate_uniqueness_of
337
+ def should_only_allow_numeric_values_for(*attributes)
338
+ warn "[DEPRECATION] should_only_allow_numeric_values_for is " <<
339
+ "deprecated. Use should_validate_numericality_of instead."
340
+ should_validate_numericality_of(*attributes)
341
+ end
458
342
 
459
- # Ensure that the has_one relationship exists. Will also test that the
460
- # associated table has the required columns. Works with polymorphic
461
- # associations.
462
- #
463
- # Options:
464
- # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
465
- #
466
- # Example:
467
- # should_have_one :god # unless hindu
468
- #
469
- def should_have_one(*associations)
470
- dependent = get_options!(associations, :dependent)
471
- klass = model_class
472
- associations.each do |association|
473
- name = "have one #{association}"
474
- name += " dependent => #{dependent}" if dependent
475
- should name do
476
- reflection = klass.reflect_on_association(association)
477
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
478
- assert_equal :has_one, reflection.macro
479
-
480
- associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
481
-
482
- if reflection.options[:foreign_key]
483
- fk = reflection.options[:foreign_key]
484
- elsif reflection.options[:as]
485
- fk = reflection.options[:as].to_s.foreign_key
486
- fk_type = fk.gsub(/_id$/, '_type')
487
- assert associated_klass.column_names.include?(fk_type),
488
- "#{associated_klass.name} does not have a #{fk_type} column."
489
- else
490
- fk = klass.name.foreign_key
491
- end
492
- assert associated_klass.column_names.include?(fk.to_s),
493
- "#{associated_klass.name} does not have a #{fk} foreign key."
494
-
495
- if dependent
496
- assert_equal dependent.to_s,
497
- reflection.options[:dependent].to_s,
498
- "#{association} should have #{dependent} dependency"
499
- end
500
- end
343
+ # Ensures that the has_many relationship exists. Will also test that the
344
+ # associated table has the required columns. Works with polymorphic
345
+ # associations.
346
+ #
347
+ # Options:
348
+ # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
349
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
350
+ #
351
+ # Example:
352
+ # should_have_many :friends
353
+ # should_have_many :enemies, :through => :friends
354
+ # should_have_many :enemies, :dependent => :destroy
355
+ #
356
+ def should_have_many(*associations)
357
+ through, dependent = get_options!(associations, :through, :dependent)
358
+ klass = model_class
359
+ associations.each do |association|
360
+ matcher = have_many(association).through(through).dependent(dependent)
361
+ should matcher.description do
362
+ assert_accepts(matcher, klass.new)
501
363
  end
502
364
  end
365
+ end
503
366
 
504
- # Ensures that the has_and_belongs_to_many relationship exists, and that the join
505
- # table is in place.
506
- #
507
- # should_have_and_belong_to_many :posts, :cars
508
- #
509
- def should_have_and_belong_to_many(*associations)
510
- get_options!(associations)
511
- klass = model_class
512
-
513
- associations.each do |association|
514
- should "should have and belong to many #{association}" do
515
- reflection = klass.reflect_on_association(association)
516
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
517
- assert_equal :has_and_belongs_to_many, reflection.macro
518
- table = reflection.options[:join_table]
519
- assert ::ActiveRecord::Base.connection.tables.include?(table.to_s), "table #{table} doesn't exist"
520
- end
367
+ # Ensure that the has_one relationship exists. Will also test that the
368
+ # associated table has the required columns. Works with polymorphic
369
+ # associations.
370
+ #
371
+ # Options:
372
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
373
+ #
374
+ # Example:
375
+ # should_have_one :god # unless hindu
376
+ #
377
+ def should_have_one(*associations)
378
+ dependent = get_options!(associations, :dependent)
379
+ klass = model_class
380
+ associations.each do |association|
381
+ matcher = have_one(association).dependent(dependent)
382
+ should matcher.description do
383
+ assert_accepts(matcher, klass.new)
521
384
  end
522
385
  end
386
+ end
523
387
 
524
- # Ensure that the belongs_to relationship exists.
525
- #
526
- # should_belong_to :parent
527
- #
528
- def should_belong_to(*associations)
529
- get_options!(associations)
530
- klass = model_class
531
- associations.each do |association|
532
- should "belong_to #{association}" do
533
- reflection = klass.reflect_on_association(association)
534
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
535
- assert_equal :belongs_to, reflection.macro
536
-
537
- unless reflection.options[:polymorphic]
538
- associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
539
- fk = reflection.options[:foreign_key] || reflection.primary_key_name
540
- assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key."
541
- end
542
- end
388
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join
389
+ # table is in place.
390
+ #
391
+ # should_have_and_belong_to_many :posts, :cars
392
+ #
393
+ def should_have_and_belong_to_many(*associations)
394
+ get_options!(associations)
395
+ klass = model_class
396
+
397
+ associations.each do |association|
398
+ matcher = have_and_belong_to_many(association)
399
+ should matcher.description do
400
+ assert_accepts(matcher, klass.new)
543
401
  end
544
402
  end
403
+ end
545
404
 
546
- # Ensure that the given class methods are defined on the model.
547
- #
548
- # should_have_class_methods :find, :destroy
549
- #
550
- def should_have_class_methods(*methods)
551
- get_options!(methods)
552
- klass = model_class
553
- methods.each do |method|
554
- should "respond to class method ##{method}" do
555
- assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
556
- end
405
+ # Ensure that the belongs_to relationship exists.
406
+ #
407
+ # should_belong_to :parent
408
+ #
409
+ def should_belong_to(*associations)
410
+ dependent = get_options!(associations, :dependent)
411
+ klass = model_class
412
+ associations.each do |association|
413
+ matcher = belong_to(association).dependent(dependent)
414
+ should matcher.description do
415
+ assert_accepts(matcher, klass.new)
557
416
  end
558
417
  end
418
+ end
559
419
 
560
- # Ensure that the given instance methods are defined on the model.
561
- #
562
- # should_have_instance_methods :email, :name, :name=
563
- #
564
- def should_have_instance_methods(*methods)
565
- get_options!(methods)
566
- klass = model_class
567
- methods.each do |method|
568
- should "respond to instance method ##{method}" do
569
- assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
570
- end
420
+ # Ensure that the given class methods are defined on the model.
421
+ #
422
+ # should_have_class_methods :find, :destroy
423
+ #
424
+ def should_have_class_methods(*methods)
425
+ get_options!(methods)
426
+ klass = model_class
427
+ methods.each do |method|
428
+ should "respond to class method ##{method}" do
429
+ assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
571
430
  end
572
431
  end
432
+ end
573
433
 
574
- # Ensure that the given columns are defined on the models backing SQL table.
575
- #
576
- # should_have_db_columns :id, :email, :name, :created_at
577
- #
578
- def should_have_db_columns(*columns)
579
- column_type = get_options!(columns, :type)
580
- klass = model_class
581
- columns.each do |name|
582
- test_name = "have column #{name}"
583
- test_name += " of type #{column_type}" if column_type
584
- should test_name do
585
- column = klass.columns.detect {|c| c.name == name.to_s }
586
- assert column, "#{klass.name} does not have column #{name}"
587
- end
434
+ # Ensure that the given instance methods are defined on the model.
435
+ #
436
+ # should_have_instance_methods :email, :name, :name=
437
+ #
438
+ def should_have_instance_methods(*methods)
439
+ get_options!(methods)
440
+ klass = model_class
441
+ methods.each do |method|
442
+ should "respond to instance method ##{method}" do
443
+ assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
588
444
  end
589
445
  end
446
+ end
590
447
 
591
- # Ensure that the given column is defined on the models backing SQL table. The options are the same as
592
- # the instance variables defined on the column definition: :precision, :limit, :default, :null,
593
- # :primary, :type, :scale, and :sql_type.
594
- #
595
- # should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
596
- # :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
597
- #
598
- def should_have_db_column(name, opts = {})
599
- klass = model_class
600
- test_name = "have column named :#{name}"
601
- test_name += " with options " + opts.inspect unless opts.empty?
602
- should test_name do
603
- column = klass.columns.detect {|c| c.name == name.to_s }
604
- assert column, "#{klass.name} does not have column #{name}"
605
- opts.each do |k, v|
606
- assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}"
607
- end
448
+ # Ensure that the given columns are defined on the models backing SQL table.
449
+ # Also aliased to should_have_index for readability.
450
+ # Takes the same options available in migrations:
451
+ # :type, :precision, :limit, :default, :null, and :scale
452
+ #
453
+ # Examples:
454
+ #
455
+ # should_have_db_columns :id, :email, :name, :created_at
456
+ #
457
+ # should_have_db_column :email, :type => "string", :limit => 255
458
+ # should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
459
+ # should_have_db_column :admin, :default => false, :null => false
460
+ #
461
+ def should_have_db_columns(*columns)
462
+ column_type, precision, limit, default, null, scale, sql_type =
463
+ get_options!(columns, :type, :precision, :limit,
464
+ :default, :null, :scale, :sql_type)
465
+ klass = model_class
466
+ columns.each do |name|
467
+ matcher = have_db_column(name).
468
+ of_type(column_type).
469
+ with_options(:precision => precision, :limit => limit,
470
+ :default => default, :null => null,
471
+ :scale => scale, :sql_type => sql_type)
472
+ should matcher.description do
473
+ assert_accepts(matcher, klass.new)
608
474
  end
609
475
  end
476
+ end
477
+
478
+ alias_method :should_have_db_column, :should_have_db_columns
610
479
 
611
- # Ensures that there are DB indices on the given columns or tuples of columns.
612
- # Also aliased to should_have_index for readability
613
- #
614
- # should_have_indices :email, :name, [:commentable_type, :commentable_id]
615
- # should_have_index :age
616
- #
617
- def should_have_indices(*columns)
618
- table = model_class.table_name
619
- indices = ::ActiveRecord::Base.connection.indexes(table).map(&:columns)
620
-
621
- columns.each do |column|
622
- should "have index on #{table} for #{column.inspect}" do
623
- columns = [column].flatten.map(&:to_s)
624
- assert_contains(indices, columns)
625
- end
480
+ # Ensures that there are DB indices on the given columns or tuples of columns.
481
+ # Also aliased to should_have_index for readability
482
+ #
483
+ # Options:
484
+ # * <tt>:unique</tt> - whether or not the index has a unique
485
+ # constraint. Use <tt>true</tt> to explicitly test for a unique
486
+ # constraint. Use <tt>false</tt> to explicitly test for a non-unique
487
+ # constraint. Use <tt>nil</tt> if you don't care whether the index is
488
+ # unique or not. Default = <tt>nil</tt>
489
+ #
490
+ # Examples:
491
+ #
492
+ # should_have_indices :email, :name, [:commentable_type, :commentable_id]
493
+ # should_have_index :age
494
+ # should_have_index :ssn, :unique => true
495
+ #
496
+ def should_have_indices(*columns)
497
+ unique = get_options!(columns, :unique)
498
+ klass = model_class
499
+
500
+ columns.each do |column|
501
+ matcher = have_index(column).unique(unique)
502
+ should matcher.description do
503
+ assert_accepts(matcher, klass.new)
626
504
  end
627
505
  end
506
+ end
628
507
 
629
- alias_method :should_have_index, :should_have_indices
630
-
631
- # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
632
- #
633
- # If an instance variable has been created in the setup named after the
634
- # model being tested, then this method will use that. Otherwise, it will
635
- # create a new instance to test against.
636
- #
637
- # Options:
638
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
639
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
640
- #
641
- # Example:
642
- # should_require_acceptance_of :eula
643
- #
644
- def should_require_acceptance_of(*attributes)
645
- message = get_options!(attributes, :message)
646
- message ||= default_error_message(:accepted)
647
- klass = model_class
648
-
649
- attributes.each do |attribute|
650
- should "require #{attribute} to be accepted" do
651
- assert_bad_value(klass, attribute, false, message)
652
- end
508
+ alias_method :should_have_index, :should_have_indices
509
+
510
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
511
+ #
512
+ # If an instance variable has been created in the setup named after the
513
+ # model being tested, then this method will use that. Otherwise, it will
514
+ # create a new instance to test against.
515
+ #
516
+ # Options:
517
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
518
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
519
+ #
520
+ # Example:
521
+ # should_validate_acceptance_of :eula
522
+ #
523
+ def should_validate_acceptance_of(*attributes)
524
+ message = get_options!(attributes, :message)
525
+ klass = model_class
526
+
527
+ attributes.each do |attribute|
528
+ matcher = validate_acceptance_of(attribute).with_message(message)
529
+ should matcher.description do
530
+ assert_accepts matcher, get_instance_of(klass)
653
531
  end
654
532
  end
533
+ end
655
534
 
656
- # Ensures that the model has a method named scope_name that returns a NamedScope object with the
657
- # proxy options set to the options you supply. scope_name can be either a symbol, or a method
658
- # call which will be evaled against the model. The eval'd method call has access to all the same
659
- # instance variables that a should statement would.
660
- #
661
- # Options: Any of the options that the named scope would pass on to find.
662
- #
663
- # Example:
664
- #
665
- # should_have_named_scope :visible, :conditions => {:visible => true}
666
- #
667
- # Passes for
668
- #
669
- # named_scope :visible, :conditions => {:visible => true}
670
- #
671
- # Or for
672
- #
673
- # def self.visible
674
- # scoped(:conditions => {:visible => true})
675
- # end
676
- #
677
- # You can test lambdas or methods that return ActiveRecord#scoped calls:
678
- #
679
- # should_have_named_scope 'recent(5)', :limit => 5
680
- # should_have_named_scope 'recent(1)', :limit => 1
681
- #
682
- # Passes for
683
- # named_scope :recent, lambda {|c| {:limit => c}}
684
- #
685
- # Or for
686
- #
687
- # def self.recent(c)
688
- # scoped(:limit => c)
689
- # end
690
- #
691
- def should_have_named_scope(scope_call, *args)
692
- klass = model_class
693
- scope_opts = args.extract_options!
694
- scope_call = scope_call.to_s
695
-
696
- context scope_call do
697
- setup do
698
- @scope = eval("#{klass}.#{scope_call}")
699
- end
700
-
701
- should "return a scope object" do
702
- assert_equal ::ActiveRecord::NamedScope::Scope, @scope.class
703
- end
704
-
705
- unless scope_opts.empty?
706
- should "scope itself to #{scope_opts.inspect}" do
707
- assert_equal scope_opts, @scope.proxy_options
708
- end
709
- end
710
- end
535
+ # Deprecated. See should_validate_uniqueness_of
536
+ def should_require_acceptance_of(*attributes)
537
+ warn "[DEPRECATION] should_require_acceptance_of is deprecated. " <<
538
+ "Use should_validate_acceptance_of instead."
539
+ should_validate_acceptance_of(*attributes)
540
+ end
541
+
542
+ # Ensures that the model has a method named scope_name that returns a NamedScope object with the
543
+ # proxy options set to the options you supply. scope_name can be either a symbol, or a method
544
+ # call which will be evaled against the model. The eval'd method call has access to all the same
545
+ # instance variables that a should statement would.
546
+ #
547
+ # Options: Any of the options that the named scope would pass on to find.
548
+ #
549
+ # Example:
550
+ #
551
+ # should_have_named_scope :visible, :conditions => {:visible => true}
552
+ #
553
+ # Passes for
554
+ #
555
+ # named_scope :visible, :conditions => {:visible => true}
556
+ #
557
+ # Or for
558
+ #
559
+ # def self.visible
560
+ # scoped(:conditions => {:visible => true})
561
+ # end
562
+ #
563
+ # You can test lambdas or methods that return ActiveRecord#scoped calls:
564
+ #
565
+ # should_have_named_scope 'recent(5)', :limit => 5
566
+ # should_have_named_scope 'recent(1)', :limit => 1
567
+ #
568
+ # Passes for
569
+ # named_scope :recent, lambda {|c| {:limit => c}}
570
+ #
571
+ # Or for
572
+ #
573
+ # def self.recent(c)
574
+ # scoped(:limit => c)
575
+ # end
576
+ #
577
+ def should_have_named_scope(scope_call, find_options = nil)
578
+ klass = model_class
579
+ matcher = have_named_scope(scope_call).finding(find_options)
580
+ should matcher.description do
581
+ assert_accepts matcher.in_context(self), klass.new
711
582
  end
712
583
  end
713
584
  end