shoulda 2.0.6 → 2.9.0

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 (84) hide show
  1. data/README.rdoc +35 -7
  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 +518 -639
  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/controller.rb +8 -8
  29. data/lib/shoulda/controller/formats/html.rb +158 -160
  30. data/lib/shoulda/controller/formats/xml.rb +132 -134
  31. data/lib/shoulda/controller/helpers.rb +51 -53
  32. data/lib/shoulda/controller/macros.rb +278 -258
  33. data/lib/shoulda/controller/resource_options.rb +211 -214
  34. data/lib/shoulda/helpers.rb +5 -7
  35. data/lib/shoulda/macros.rb +63 -64
  36. data/lib/shoulda/private_helpers.rb +16 -18
  37. data/lib/shoulda/rails.rb +1 -8
  38. data/lib/shoulda/rspec.rb +5 -0
  39. data/lib/shoulda/tasks/list_tests.rake +6 -1
  40. data/lib/shoulda/test_unit.rb +19 -0
  41. data/rails/init.rb +1 -1
  42. data/test/README +2 -2
  43. data/test/fail_macros.rb +16 -16
  44. data/test/functional/posts_controller_test.rb +5 -2
  45. data/test/matchers/allow_mass_assignment_of_matcher_test.rb +68 -0
  46. data/test/matchers/allow_value_matcher_test.rb +41 -0
  47. data/test/matchers/association_matcher_test.rb +258 -0
  48. data/test/matchers/ensure_inclusion_of_matcher_test.rb +80 -0
  49. data/test/matchers/ensure_length_of_matcher_test.rb +158 -0
  50. data/test/matchers/have_db_column_matcher_test.rb +169 -0
  51. data/test/matchers/have_index_matcher_test.rb +74 -0
  52. data/test/matchers/have_named_scope_matcher_test.rb +65 -0
  53. data/test/matchers/have_readonly_attributes_matcher_test.rb +29 -0
  54. data/test/matchers/validate_acceptance_of_matcher_test.rb +44 -0
  55. data/test/matchers/validate_numericality_of_matcher_test.rb +52 -0
  56. data/test/matchers/validate_presence_of_matcher_test.rb +86 -0
  57. data/test/matchers/validate_uniqueness_of_matcher_test.rb +141 -0
  58. data/test/model_builder.rb +61 -0
  59. data/test/other/autoload_macro_test.rb +18 -0
  60. data/test/other/helpers_test.rb +58 -0
  61. data/test/other/private_helpers_test.rb +1 -1
  62. data/test/other/should_test.rb +16 -16
  63. data/test/rails_root/app/controllers/posts_controller.rb +6 -5
  64. data/test/rails_root/app/models/pets/dog.rb +10 -0
  65. data/test/rails_root/app/models/treat.rb +3 -0
  66. data/test/rails_root/app/models/user.rb +2 -2
  67. data/test/rails_root/app/views/layouts/posts.rhtml +2 -0
  68. data/test/rails_root/config/database.yml +1 -1
  69. data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  70. data/test/rails_root/db/migrate/001_create_users.rb +3 -2
  71. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  72. data/test/rails_root/log/test.log +0 -0
  73. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  74. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  75. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  76. data/test/test_helper.rb +3 -1
  77. data/test/unit/address_test.rb +1 -1
  78. data/test/unit/dog_test.rb +5 -2
  79. data/test/unit/post_test.rb +7 -3
  80. data/test/unit/product_test.rb +2 -2
  81. data/test/unit/tag_test.rb +2 -1
  82. data/test/unit/user_test.rb +17 -8
  83. metadata +44 -4
  84. 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
 
@@ -54,8 +55,8 @@ Quick macro tests for your ActiveRecord associations and validations:
54
55
  should_have_many :tags, :through => :taggings
55
56
 
56
57
  should_require_unique_attributes :title
57
- should_require_attributes :body, :message => /wtf/
58
- should_require_attributes :title
58
+ should_validate_presence_of :body, :message => /wtf/
59
+ should_validate_presence_of :title
59
60
  should_only_allow_numeric_values_for :user_id
60
61
  end
61
62
 
@@ -71,7 +72,7 @@ Quick macro tests for your ActiveRecord associations and validations:
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.0"
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,592 @@
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]
17
- end
18
- end
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
+ # <b>DEPRECATED:</b> Use <tt>fixtures :all</tt> instead
27
+ #
28
+ # Loads all fixture files (<tt>test/fixtures/*.yml</tt>)
29
+ def load_all_fixtures
30
+ warn "[DEPRECATION] load_all_fixtures is deprecated. Use `fixtures :all` instead."
31
+ fixtures :all
19
32
  end
20
33
 
21
- # = Macro test helpers for your active record models
34
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
22
35
  #
23
- # These helpers will test most of the validations and associations for your ActiveRecord models.
36
+ # If an instance variable has been created in the setup named after the
37
+ # model being tested, then this method will use that. Otherwise, it will
38
+ # create a new instance to test against.
24
39
  #
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"
40
+ # Options:
41
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
42
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
29
43
  #
30
- # should_protect_attributes :password
44
+ # Example:
45
+ # should_validate_presence_of :name, :phone_number
31
46
  #
32
- # should_have_one :profile
33
- # should_have_many :dogs
34
- # should_have_many :messes, :through => :dogs
35
- # should_belong_to :lover
36
- # end
47
+ def should_validate_presence_of(*attributes)
48
+ message = get_options!(attributes, :message)
49
+ klass = model_class
50
+
51
+ attributes.each do |attribute|
52
+ matcher = validate_presence_of(attribute).with_message(message)
53
+ should matcher.description do
54
+ assert_accepts(matcher, get_instance_of(klass))
55
+ end
56
+ end
57
+ end
58
+
59
+ # Deprecated. See should_validate_presence_of
60
+ def should_require_attributes(*attributes)
61
+ warn "[DEPRECATION] should_require_attributes is deprecated. " <<
62
+ "Use should_validate_presence_of instead."
63
+ should_validate_presence_of(*attributes)
64
+ end
65
+
66
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
67
+ # Requires an existing record
68
+ #
69
+ # Options:
70
+
71
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
72
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
73
+ # * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
74
+ # * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
75
+ # exact match. Ignored by non-text attributes. Default = <tt>true</tt>
37
76
  #
38
- # For all of these helpers, the last parameter may be a hash of options.
77
+ # Examples:
78
+ # should_validate_uniqueness_of :keyword, :username
79
+ # should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
80
+ # should_validate_uniqueness_of :email, :scoped_to => :name
81
+ # should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
82
+ # should_validate_uniqueness_of :email, :case_sensitive => false
39
83
  #
40
- module Macros
41
- include MacroHelpers
84
+ def should_validate_uniqueness_of(*attributes)
85
+ message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
86
+ scope = [*scope].compact
87
+ case_sensitive = true if case_sensitive.nil?
42
88
 
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
89
+ klass = model_class
50
90
 
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
91
+ attributes.each do |attribute|
92
+ matcher = validate_uniqueness_of(attribute).
93
+ with_message(message).scoped_to(scope)
94
+ matcher = matcher.case_insensitive unless case_sensitive
95
+ should matcher.description do
96
+ assert_accepts(matcher, get_instance_of(klass))
73
97
  end
74
98
  end
99
+ end
75
100
 
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
101
+ # Deprecated. See should_validate_uniqueness_of
102
+ def should_require_unique_attributes(*attributes)
103
+ warn "[DEPRECATION] should_require_unique_attributes is deprecated. " <<
104
+ "Use should_validate_uniqueness_of instead."
105
+ should_validate_uniqueness_of(*attributes)
106
+ end
126
107
 
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
108
+ # Ensures that the attribute can be set on mass update.
109
+ #
110
+ # should_allow_mass_assignment_of :first_name, :last_name
111
+ #
112
+ def should_allow_mass_assignment_of(*attributes)
113
+ get_options!(attributes)
114
+ klass = model_class
149
115
 
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
116
+ attributes.each do |attribute|
117
+ matcher = allow_mass_assignment_of(attribute)
118
+ should matcher.description do
119
+ assert_accepts matcher, klass.new
168
120
  end
169
121
  end
122
+ end
170
123
 
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
124
+ # Ensures that the attribute cannot be set on mass update.
125
+ #
126
+ # should_not_allow_mass_assignment_of :password, :admin_flag
127
+ #
128
+ def should_not_allow_mass_assignment_of(*attributes)
129
+ get_options!(attributes)
130
+ klass = model_class
194
131
 
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
132
+ attributes.each do |attribute|
133
+ matcher = allow_mass_assignment_of(attribute)
134
+ should "not #{matcher.description}" do
135
+ assert_rejects matcher, klass.new
211
136
  end
212
137
  end
138
+ end
213
139
 
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
140
+ # Deprecated. See should_not_allow_mass_assignment_of
141
+ def should_protect_attributes(*attributes)
142
+ warn "[DEPRECATION] should_protect_attributes is deprecated. " <<
143
+ "Use should_not_allow_mass_assignment_of instead."
144
+ should_not_allow_mass_assignment_of(*attributes)
145
+ end
252
146
 
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
147
+ # Ensures that the attribute cannot be changed once the record has been created.
148
+ #
149
+ # should_have_readonly_attributes :password, :admin_flag
150
+ #
151
+ def should_have_readonly_attributes(*attributes)
152
+ get_options!(attributes)
153
+ klass = model_class
257
154
 
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
155
+ attributes.each do |attribute|
156
+ matcher = have_readonly_attribute(attribute)
157
+ should matcher.description do
158
+ assert_accepts matcher, klass.new
263
159
  end
264
160
  end
161
+ end
265
162
 
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)
163
+ # Ensures that the attribute cannot be set to the given values
164
+ #
165
+ # If an instance variable has been created in the setup named after the
166
+ # model being tested, then this method will use that. Otherwise, it will
167
+ # create a new instance to test against.
168
+ #
169
+ # Options:
170
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
171
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
172
+ #
173
+ # Example:
174
+ # should_not_allow_values_for :isbn, "bad 1", "bad 2"
175
+ #
176
+ def should_not_allow_values_for(attribute, *bad_values)
177
+ message = get_options!(bad_values, :message)
178
+ klass = model_class
179
+ bad_values.each do |value|
180
+ matcher = allow_value(value).for(attribute).with_message(message)
181
+ should "not #{matcher.description}" do
182
+ assert_rejects matcher, get_instance_of(klass)
294
183
  end
295
184
  end
185
+ end
296
186
 
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)
187
+ # Ensures that the attribute can be set to the given values.
188
+ #
189
+ # If an instance variable has been created in the setup named after the
190
+ # model being tested, then this method will use that. Otherwise, it will
191
+ # create a new instance to test against.
192
+ #
193
+ # Example:
194
+ # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
195
+ #
196
+ def should_allow_values_for(attribute, *good_values)
197
+ get_options!(good_values)
198
+ klass = model_class
199
+ klass = model_class
200
+ good_values.each do |value|
201
+ matcher = allow_value(value).for(attribute)
202
+ should matcher.description do
203
+ assert_accepts matcher, get_instance_of(klass)
324
204
  end
205
+ end
206
+ end
325
207
 
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
208
+ # Ensures that the length of the attribute is in the given range
209
+ #
210
+ # If an instance variable has been created in the setup named after the
211
+ # model being tested, then this method will use that. Otherwise, it will
212
+ # create a new instance to test against.
213
+ #
214
+ # Options:
215
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
216
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
217
+ # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
218
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
219
+ #
220
+ # Example:
221
+ # should_ensure_length_in_range :password, (6..20)
222
+ #
223
+ def should_ensure_length_in_range(attribute, range, opts = {})
224
+ short_message, long_message = get_options!([opts],
225
+ :short_message,
226
+ :long_message)
227
+ klass = model_class
228
+
229
+ matcher = ensure_length_of(attribute).
230
+ is_at_least(range.first).
231
+ with_short_message(short_message).
232
+ is_at_most(range.last).
233
+ with_long_message(long_message)
234
+
235
+ should matcher.description do
236
+ assert_accepts matcher, get_instance_of(klass)
330
237
  end
238
+ end
331
239
 
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
240
+ # Ensures that the length of the attribute is at least a certain length
241
+ #
242
+ # If an instance variable has been created in the setup named after the
243
+ # model being tested, then this method will use that. Otherwise, it will
244
+ # create a new instance to test against.
245
+ #
246
+ # Options:
247
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
248
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
249
+ #
250
+ # Example:
251
+ # should_ensure_length_at_least :name, 3
252
+ #
253
+ def should_ensure_length_at_least(attribute, min_length, opts = {})
254
+ short_message = get_options!([opts], :short_message)
255
+ klass = model_class
360
256
 
361
- should "allow #{attribute} to be #{min}" do
362
- v = min
363
- assert_good_value(klass, attribute, v, low_message)
364
- end
257
+ matcher = ensure_length_of(attribute).
258
+ is_at_least(min_length).
259
+ with_short_message(short_message)
365
260
 
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
261
+ should matcher.description do
262
+ assert_accepts matcher, get_instance_of(klass)
263
+ end
264
+ end
370
265
 
371
- should "allow #{attribute} to be #{max}" do
372
- v = max
373
- assert_good_value(klass, attribute, v, high_message)
374
- end
266
+ # Ensures that the length of the attribute is exactly 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>: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.wrong_length') % length</tt>
275
+ #
276
+ # Example:
277
+ # should_ensure_length_is :ssn, 9
278
+ #
279
+ def should_ensure_length_is(attribute, length, opts = {})
280
+ message = get_options!([opts], :message)
281
+ klass = model_class
282
+ matcher = ensure_length_of(attribute).
283
+ is_equal_to(length).
284
+ with_message(message)
285
+
286
+ should matcher.description do
287
+ assert_accepts matcher, get_instance_of(klass)
375
288
  end
289
+ end
376
290
 
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
291
+ # Ensure that the attribute is in the range specified
292
+ #
293
+ # If an instance variable has been created in the setup named after the
294
+ # model being tested, then this method will use that. Otherwise, it will
295
+ # create a new instance to test against.
296
+ #
297
+ # Options:
298
+ # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
299
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
300
+ # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
301
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
302
+ #
303
+ # Example:
304
+ # should_ensure_value_in_range :age, (0..100)
305
+ #
306
+ def should_ensure_value_in_range(attribute, range, opts = {})
307
+ message = get_options!([opts], :message)
308
+ message ||= default_error_message(:inclusion)
309
+
310
+ klass = model_class
311
+ matcher = ensure_inclusion_of(attribute).
312
+ in_range(range).
313
+ with_message(message)
314
+ should matcher.description do
315
+ assert_accepts matcher, get_instance_of(klass)
400
316
  end
317
+ end
401
318
 
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
319
+ # Ensure that the attribute is numeric
320
+ #
321
+ # If an instance variable has been created in the setup named after the
322
+ # model being tested, then this method will use that. Otherwise, it will
323
+ # create a new instance to test against.
324
+ #
325
+ # Options:
326
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
327
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
328
+ #
329
+ # Example:
330
+ # should_validate_numericality_of :age
331
+ #
332
+ def should_validate_numericality_of(*attributes)
333
+ message = get_options!(attributes, :message)
334
+ klass = model_class
335
+ attributes.each do |attribute|
336
+ matcher = validate_numericality_of(attribute).
337
+ with_message(message)
338
+ should matcher.description do
339
+ assert_accepts matcher, get_instance_of(klass)
456
340
  end
457
341
  end
342
+ end
458
343
 
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
344
+ # Deprecated. See should_validate_uniqueness_of
345
+ def should_only_allow_numeric_values_for(*attributes)
346
+ warn "[DEPRECATION] should_only_allow_numeric_values_for is " <<
347
+ "deprecated. Use should_validate_numericality_of instead."
348
+ should_validate_numericality_of(*attributes)
349
+ end
350
+
351
+ # Ensures that the has_many relationship exists. Will also test that the
352
+ # associated table has the required columns. Works with polymorphic
353
+ # associations.
354
+ #
355
+ # Options:
356
+ # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
357
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
358
+ #
359
+ # Example:
360
+ # should_have_many :friends
361
+ # should_have_many :enemies, :through => :friends
362
+ # should_have_many :enemies, :dependent => :destroy
363
+ #
364
+ def should_have_many(*associations)
365
+ through, dependent = get_options!(associations, :through, :dependent)
366
+ klass = model_class
367
+ associations.each do |association|
368
+ matcher = have_many(association).through(through).dependent(dependent)
369
+ should matcher.description do
370
+ assert_accepts(matcher, klass.new)
501
371
  end
502
372
  end
373
+ end
503
374
 
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
375
+ # Ensure that the has_one relationship exists. Will also test that the
376
+ # associated table has the required columns. Works with polymorphic
377
+ # associations.
378
+ #
379
+ # Options:
380
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
381
+ #
382
+ # Example:
383
+ # should_have_one :god # unless hindu
384
+ #
385
+ def should_have_one(*associations)
386
+ dependent = get_options!(associations, :dependent)
387
+ klass = model_class
388
+ associations.each do |association|
389
+ matcher = have_one(association).dependent(dependent)
390
+ should matcher.description do
391
+ assert_accepts(matcher, klass.new)
521
392
  end
522
393
  end
394
+ end
523
395
 
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
396
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join
397
+ # table is in place.
398
+ #
399
+ # should_have_and_belong_to_many :posts, :cars
400
+ #
401
+ def should_have_and_belong_to_many(*associations)
402
+ get_options!(associations)
403
+ klass = model_class
404
+
405
+ associations.each do |association|
406
+ matcher = have_and_belong_to_many(association)
407
+ should matcher.description do
408
+ assert_accepts(matcher, klass.new)
543
409
  end
544
410
  end
411
+ end
545
412
 
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
413
+ # Ensure that the belongs_to relationship exists.
414
+ #
415
+ # should_belong_to :parent
416
+ #
417
+ def should_belong_to(*associations)
418
+ dependent = get_options!(associations, :dependent)
419
+ klass = model_class
420
+ associations.each do |association|
421
+ matcher = belong_to(association).dependent(dependent)
422
+ should matcher.description do
423
+ assert_accepts(matcher, klass.new)
557
424
  end
558
425
  end
426
+ end
559
427
 
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
428
+ # Ensure that the given class methods are defined on the model.
429
+ #
430
+ # should_have_class_methods :find, :destroy
431
+ #
432
+ def should_have_class_methods(*methods)
433
+ get_options!(methods)
434
+ klass = model_class
435
+ methods.each do |method|
436
+ should "respond to class method ##{method}" do
437
+ assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
571
438
  end
572
439
  end
440
+ end
573
441
 
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
442
+ # Ensure that the given instance methods are defined on the model.
443
+ #
444
+ # should_have_instance_methods :email, :name, :name=
445
+ #
446
+ def should_have_instance_methods(*methods)
447
+ get_options!(methods)
448
+ klass = model_class
449
+ methods.each do |method|
450
+ should "respond to instance method ##{method}" do
451
+ assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
588
452
  end
589
453
  end
454
+ end
590
455
 
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
456
+ # Ensure that the given columns are defined on the models backing SQL table.
457
+ # Also aliased to should_have_index for readability.
458
+ # Takes the same options available in migrations:
459
+ # :type, :precision, :limit, :default, :null, and :scale
460
+ #
461
+ # Examples:
462
+ #
463
+ # should_have_db_columns :id, :email, :name, :created_at
464
+ #
465
+ # should_have_db_column :email, :type => "string", :limit => 255
466
+ # should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
467
+ # should_have_db_column :admin, :default => false, :null => false
468
+ #
469
+ def should_have_db_columns(*columns)
470
+ column_type, precision, limit, default, null, scale, sql_type =
471
+ get_options!(columns, :type, :precision, :limit,
472
+ :default, :null, :scale, :sql_type)
473
+ klass = model_class
474
+ columns.each do |name|
475
+ matcher = have_db_column(name).
476
+ of_type(column_type).
477
+ with_options(:precision => precision, :limit => limit,
478
+ :default => default, :null => null,
479
+ :scale => scale, :sql_type => sql_type)
480
+ should matcher.description do
481
+ assert_accepts(matcher, klass.new)
608
482
  end
609
483
  end
484
+ end
485
+
486
+ alias_method :should_have_db_column, :should_have_db_columns
610
487
 
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
488
+ # Ensures that there are DB indices on the given columns or tuples of columns.
489
+ # Also aliased to should_have_index for readability
490
+ #
491
+ # Options:
492
+ # * <tt>:unique</tt> - whether or not the index has a unique
493
+ # constraint. Use <tt>true</tt> to explicitly test for a unique
494
+ # constraint. Use <tt>false</tt> to explicitly test for a non-unique
495
+ # constraint. Use <tt>nil</tt> if you don't care whether the index is
496
+ # unique or not. Default = <tt>nil</tt>
497
+ #
498
+ # Examples:
499
+ #
500
+ # should_have_indices :email, :name, [:commentable_type, :commentable_id]
501
+ # should_have_index :age
502
+ # should_have_index :ssn, :unique => true
503
+ #
504
+ def should_have_indices(*columns)
505
+ unique = get_options!(columns, :unique)
506
+ klass = model_class
507
+
508
+ columns.each do |column|
509
+ matcher = have_index(column).unique(unique)
510
+ should matcher.description do
511
+ assert_accepts(matcher, klass.new)
626
512
  end
627
513
  end
514
+ end
628
515
 
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
516
+ alias_method :should_have_index, :should_have_indices
517
+
518
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
519
+ #
520
+ # If an instance variable has been created in the setup named after the
521
+ # model being tested, then this method will use that. Otherwise, it will
522
+ # create a new instance to test against.
523
+ #
524
+ # Options:
525
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
526
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
527
+ #
528
+ # Example:
529
+ # should_validate_acceptance_of :eula
530
+ #
531
+ def should_validate_acceptance_of(*attributes)
532
+ message = get_options!(attributes, :message)
533
+ klass = model_class
534
+
535
+ attributes.each do |attribute|
536
+ matcher = validate_acceptance_of(attribute).with_message(message)
537
+ should matcher.description do
538
+ assert_accepts matcher, get_instance_of(klass)
653
539
  end
654
540
  end
541
+ end
655
542
 
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
543
+ # Deprecated. See should_validate_uniqueness_of
544
+ def should_require_acceptance_of(*attributes)
545
+ warn "[DEPRECATION] should_require_acceptance_of is deprecated. " <<
546
+ "Use should_validate_acceptance_of instead."
547
+ should_validate_acceptance_of(*attributes)
548
+ end
549
+
550
+ # Ensures that the model has a method named scope_name that returns a NamedScope object with the
551
+ # proxy options set to the options you supply. scope_name can be either a symbol, or a method
552
+ # call which will be evaled against the model. The eval'd method call has access to all the same
553
+ # instance variables that a should statement would.
554
+ #
555
+ # Options: Any of the options that the named scope would pass on to find.
556
+ #
557
+ # Example:
558
+ #
559
+ # should_have_named_scope :visible, :conditions => {:visible => true}
560
+ #
561
+ # Passes for
562
+ #
563
+ # named_scope :visible, :conditions => {:visible => true}
564
+ #
565
+ # Or for
566
+ #
567
+ # def self.visible
568
+ # scoped(:conditions => {:visible => true})
569
+ # end
570
+ #
571
+ # You can test lambdas or methods that return ActiveRecord#scoped calls:
572
+ #
573
+ # should_have_named_scope 'recent(5)', :limit => 5
574
+ # should_have_named_scope 'recent(1)', :limit => 1
575
+ #
576
+ # Passes for
577
+ # named_scope :recent, lambda {|c| {:limit => c}}
578
+ #
579
+ # Or for
580
+ #
581
+ # def self.recent(c)
582
+ # scoped(:limit => c)
583
+ # end
584
+ #
585
+ def should_have_named_scope(scope_call, find_options = nil)
586
+ klass = model_class
587
+ matcher = have_named_scope(scope_call).finding(find_options)
588
+ should matcher.description do
589
+ assert_accepts matcher.in_context(self), klass.new
711
590
  end
712
591
  end
713
592
  end