tpitale-shoulda 2.11.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 (170) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +10 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +171 -0
  4. data/Rakefile +71 -0
  5. data/bin/convert_to_should_syntax +42 -0
  6. data/lib/shoulda.rb +11 -0
  7. data/lib/shoulda/action_controller.rb +26 -0
  8. data/lib/shoulda/action_controller/macros.rb +240 -0
  9. data/lib/shoulda/action_controller/matchers.rb +37 -0
  10. data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +109 -0
  11. data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +57 -0
  12. data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +81 -0
  13. data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +74 -0
  14. data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +81 -0
  15. data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
  16. data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +87 -0
  17. data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
  18. data/lib/shoulda/action_mailer.rb +10 -0
  19. data/lib/shoulda/action_mailer/assertions.rb +38 -0
  20. data/lib/shoulda/action_view.rb +10 -0
  21. data/lib/shoulda/action_view/macros.rb +61 -0
  22. data/lib/shoulda/active_record.rb +16 -0
  23. data/lib/shoulda/active_record/assertions.rb +69 -0
  24. data/lib/shoulda/active_record/helpers.rb +27 -0
  25. data/lib/shoulda/active_record/macros.rb +512 -0
  26. data/lib/shoulda/active_record/matchers.rb +43 -0
  27. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  28. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
  29. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  30. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  31. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  32. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  33. data/lib/shoulda/active_record/matchers/have_db_index_matcher.rb +112 -0
  34. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +128 -0
  35. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  36. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  37. data/lib/shoulda/active_record/matchers/validate_format_of_matcher.rb +67 -0
  38. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  39. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  40. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  41. data/lib/shoulda/active_record/matchers/validation_matcher.rb +57 -0
  42. data/lib/shoulda/assertions.rb +71 -0
  43. data/lib/shoulda/autoload_macros.rb +46 -0
  44. data/lib/shoulda/context.rb +413 -0
  45. data/lib/shoulda/helpers.rb +8 -0
  46. data/lib/shoulda/macros.rb +133 -0
  47. data/lib/shoulda/minitest.rb +24 -0
  48. data/lib/shoulda/private_helpers.rb +13 -0
  49. data/lib/shoulda/proc_extensions.rb +14 -0
  50. data/lib/shoulda/rails.rb +13 -0
  51. data/lib/shoulda/rspec.rb +11 -0
  52. data/lib/shoulda/tasks.rb +3 -0
  53. data/lib/shoulda/tasks/list_tests.rake +29 -0
  54. data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
  55. data/lib/shoulda/test_unit.rb +22 -0
  56. data/rails/init.rb +7 -0
  57. data/test/README +36 -0
  58. data/test/fail_macros.rb +39 -0
  59. data/test/fixtures/addresses.yml +3 -0
  60. data/test/fixtures/friendships.yml +0 -0
  61. data/test/fixtures/posts.yml +5 -0
  62. data/test/fixtures/products.yml +0 -0
  63. data/test/fixtures/taggings.yml +0 -0
  64. data/test/fixtures/tags.yml +9 -0
  65. data/test/fixtures/users.yml +6 -0
  66. data/test/functional/posts_controller_test.rb +121 -0
  67. data/test/functional/users_controller_test.rb +19 -0
  68. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
  69. data/test/matchers/active_record/allow_value_matcher_test.rb +64 -0
  70. data/test/matchers/active_record/association_matcher_test.rb +263 -0
  71. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
  72. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
  73. data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
  74. data/test/matchers/active_record/have_db_index_matcher_test.rb +91 -0
  75. data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
  76. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
  77. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
  78. data/test/matchers/active_record/validate_format_of_matcher_test.rb +39 -0
  79. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
  80. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
  81. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
  82. data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
  83. data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
  84. data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
  85. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +32 -0
  86. data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
  87. data/test/matchers/controller/route_matcher_test.rb +58 -0
  88. data/test/matchers/controller/set_session_matcher_test.rb +38 -0
  89. data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
  90. data/test/model_builder.rb +106 -0
  91. data/test/other/autoload_macro_test.rb +18 -0
  92. data/test/other/context_test.rb +203 -0
  93. data/test/other/convert_to_should_syntax_test.rb +63 -0
  94. data/test/other/helpers_test.rb +340 -0
  95. data/test/other/private_helpers_test.rb +32 -0
  96. data/test/other/should_test.rb +271 -0
  97. data/test/rails_root/app/controllers/application_controller.rb +25 -0
  98. data/test/rails_root/app/controllers/posts_controller.rb +87 -0
  99. data/test/rails_root/app/controllers/users_controller.rb +84 -0
  100. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  101. data/test/rails_root/app/helpers/posts_helper.rb +2 -0
  102. data/test/rails_root/app/helpers/users_helper.rb +2 -0
  103. data/test/rails_root/app/models/address.rb +7 -0
  104. data/test/rails_root/app/models/flea.rb +3 -0
  105. data/test/rails_root/app/models/friendship.rb +4 -0
  106. data/test/rails_root/app/models/pets/cat.rb +7 -0
  107. data/test/rails_root/app/models/pets/dog.rb +10 -0
  108. data/test/rails_root/app/models/post.rb +12 -0
  109. data/test/rails_root/app/models/product.rb +12 -0
  110. data/test/rails_root/app/models/profile.rb +2 -0
  111. data/test/rails_root/app/models/registration.rb +2 -0
  112. data/test/rails_root/app/models/tag.rb +8 -0
  113. data/test/rails_root/app/models/tagging.rb +4 -0
  114. data/test/rails_root/app/models/treat.rb +3 -0
  115. data/test/rails_root/app/models/user.rb +32 -0
  116. data/test/rails_root/app/views/layouts/posts.rhtml +19 -0
  117. data/test/rails_root/app/views/layouts/users.rhtml +17 -0
  118. data/test/rails_root/app/views/layouts/wide.html.erb +1 -0
  119. data/test/rails_root/app/views/posts/edit.rhtml +27 -0
  120. data/test/rails_root/app/views/posts/index.rhtml +25 -0
  121. data/test/rails_root/app/views/posts/new.rhtml +26 -0
  122. data/test/rails_root/app/views/posts/show.rhtml +18 -0
  123. data/test/rails_root/app/views/users/edit.rhtml +22 -0
  124. data/test/rails_root/app/views/users/index.rhtml +22 -0
  125. data/test/rails_root/app/views/users/new.rhtml +21 -0
  126. data/test/rails_root/app/views/users/show.rhtml +13 -0
  127. data/test/rails_root/config/boot.rb +110 -0
  128. data/test/rails_root/config/database.yml +4 -0
  129. data/test/rails_root/config/environment.rb +18 -0
  130. data/test/rails_root/config/environments/test.rb +0 -0
  131. data/test/rails_root/config/initializers/new_rails_defaults.rb +15 -0
  132. data/test/rails_root/config/initializers/shoulda.rb +8 -0
  133. data/test/rails_root/config/routes.rb +6 -0
  134. data/test/rails_root/db/migrate/001_create_users.rb +19 -0
  135. data/test/rails_root/db/migrate/002_create_posts.rb +13 -0
  136. data/test/rails_root/db/migrate/003_create_taggings.rb +12 -0
  137. data/test/rails_root/db/migrate/004_create_tags.rb +11 -0
  138. data/test/rails_root/db/migrate/005_create_dogs.rb +12 -0
  139. data/test/rails_root/db/migrate/006_create_addresses.rb +14 -0
  140. data/test/rails_root/db/migrate/007_create_fleas.rb +11 -0
  141. data/test/rails_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  142. data/test/rails_root/db/migrate/009_create_products.rb +17 -0
  143. data/test/rails_root/db/migrate/010_create_friendships.rb +14 -0
  144. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  145. data/test/rails_root/db/migrate/20090506203502_create_profiles.rb +12 -0
  146. data/test/rails_root/db/migrate/20090506203536_create_registrations.rb +14 -0
  147. data/test/rails_root/db/migrate/20090513104502_create_cats.rb +12 -0
  148. data/test/rails_root/db/schema.rb +0 -0
  149. data/test/rails_root/log/test.log +1 -0
  150. data/test/rails_root/public/404.html +30 -0
  151. data/test/rails_root/public/422.html +30 -0
  152. data/test/rails_root/public/500.html +30 -0
  153. data/test/rails_root/script/console +3 -0
  154. data/test/rails_root/script/generate +3 -0
  155. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  156. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  157. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  158. data/test/rspec_test.rb +207 -0
  159. data/test/test_helper.rb +28 -0
  160. data/test/unit/address_test.rb +10 -0
  161. data/test/unit/cat_test.rb +7 -0
  162. data/test/unit/dog_test.rb +9 -0
  163. data/test/unit/flea_test.rb +6 -0
  164. data/test/unit/friendship_test.rb +6 -0
  165. data/test/unit/post_test.rb +19 -0
  166. data/test/unit/product_test.rb +23 -0
  167. data/test/unit/tag_test.rb +15 -0
  168. data/test/unit/tagging_test.rb +6 -0
  169. data/test/unit/user_test.rb +80 -0
  170. metadata +226 -0
@@ -0,0 +1,87 @@
1
+ module Shoulda # :nodoc:
2
+ module ActionController # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that a session key was set to the expected value.
6
+ #
7
+ # Example:
8
+ #
9
+ # it { should set_session(:message) }
10
+ # it { should set_session(:user_id).to(@user.id) }
11
+ # it { should_not set_session(:user_id) }
12
+ def set_session(key)
13
+ SetSessionMatcher.new(key)
14
+ end
15
+
16
+ class SetSessionMatcher # :nodoc:
17
+
18
+ def initialize(key)
19
+ @key = key.to_s
20
+ end
21
+
22
+ def to(value)
23
+ @value = value
24
+ self
25
+ end
26
+
27
+ def matches?(controller)
28
+ @controller = controller
29
+ (assigned_value? && assigned_correct_value?) || cleared_value?
30
+ end
31
+
32
+ def failure_message
33
+ "Expected #{expectation}, but #{result}"
34
+ end
35
+
36
+ def negative_failure_message
37
+ "Didn't expect #{expectation}, but #{result}"
38
+ end
39
+
40
+ def description
41
+ description = "set session variable #{@key.inspect}"
42
+ description << " to #{@value.inspect}" if defined?(@value)
43
+ description
44
+ end
45
+
46
+ private
47
+
48
+ def assigned_value?
49
+ !assigned_value.nil?
50
+ end
51
+
52
+ def cleared_value?
53
+ defined?(@value) && @value.nil? && assigned_value.nil?
54
+ end
55
+
56
+ def assigned_correct_value?
57
+ return true if @value.nil?
58
+ assigned_value == @value
59
+ end
60
+
61
+ def assigned_value
62
+ session[@key]
63
+ end
64
+
65
+ def session
66
+ @controller.response.session.data
67
+ end
68
+
69
+ def expectation
70
+ expectation = "session variable #{@key} to be set"
71
+ expectation << " to #{@value.inspect}" if @value
72
+ expectation
73
+ end
74
+
75
+ def result
76
+ if session.empty?
77
+ "no session variables were set"
78
+ else
79
+ "the session was #{session.inspect}"
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,85 @@
1
+ module Shoulda # :nodoc:
2
+ module ActionController # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the flash contains the given value. Can be a String, a
6
+ # Regexp, or nil (indicating that the flash should not be set).
7
+ #
8
+ # Example:
9
+ #
10
+ # it { should set_the_flash }
11
+ # it { should set_the_flash.to("Thank you for placing this order.") }
12
+ # it { should set_the_flash.to(/created/i) }
13
+ # it { should_not set_the_flash }
14
+ def set_the_flash
15
+ SetTheFlashMatcher.new
16
+ end
17
+
18
+ class SetTheFlashMatcher # :nodoc:
19
+
20
+ def to(value)
21
+ @value = value
22
+ self
23
+ end
24
+
25
+ def matches?(controller)
26
+ @controller = controller
27
+ sets_the_flash? && string_value_matches? && regexp_value_matches?
28
+ end
29
+
30
+ attr_reader :failure_message, :negative_failure_message
31
+
32
+ def description
33
+ description = "set the flash"
34
+ description << " to #{@value.inspect}" unless @value.nil?
35
+ description
36
+ end
37
+
38
+ def failure_message
39
+ "Expected #{expectation}"
40
+ end
41
+
42
+ def negative_failure_message
43
+ "Did not expect #{expectation}"
44
+ end
45
+
46
+ private
47
+
48
+ def sets_the_flash?
49
+ !flash.blank?
50
+ end
51
+
52
+ def string_value_matches?
53
+ return true unless String === @value
54
+ flash.values.any? {|value| value == @value }
55
+ end
56
+
57
+ def regexp_value_matches?
58
+ return true unless Regexp === @value
59
+ flash.values.any? {|value| value =~ @value }
60
+ end
61
+
62
+ def flash
63
+ @controller.response.session['flash']
64
+ end
65
+
66
+ def expectation
67
+ expectation = "the flash to be set"
68
+ expectation << " to #{@value.inspect}" unless @value.nil?
69
+ expectation << ", but #{flash_description}"
70
+ expectation
71
+ end
72
+
73
+ def flash_description
74
+ if flash.blank?
75
+ "no flash was set"
76
+ else
77
+ "was #{flash.inspect}"
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,10 @@
1
+ require 'shoulda'
2
+ require 'shoulda/action_mailer/assertions'
3
+
4
+ module Test # :nodoc: all
5
+ module Unit
6
+ class TestCase
7
+ include Shoulda::ActionMailer::Assertions
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,38 @@
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."
24
+ end
25
+ end
26
+
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
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,10 @@
1
+ require 'shoulda'
2
+ require 'shoulda/action_view/macros'
3
+
4
+ module Test # :nodoc: all
5
+ module Unit
6
+ class TestCase
7
+ extend Shoulda::ActionView::Macros
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,61 @@
1
+ module Shoulda # :nodoc:
2
+ module ActionView # :nodoc:
3
+ # = Macro test helpers for your view
4
+ #
5
+ # By using the macro helpers you can quickly and easily create concise and
6
+ # easy to read test suites.
7
+ #
8
+ # This code segment:
9
+ # context "on GET to :new" do
10
+ # setup do
11
+ # get :new
12
+ # end
13
+ #
14
+ # should_render_page_with_metadata :title => /index/
15
+ #
16
+ # should "do something else really cool" do
17
+ # assert_select '#really_cool'
18
+ # end
19
+ # end
20
+ #
21
+ # Would produce 3 tests for the +show+ action
22
+ module Macros
23
+
24
+ # Macro that creates a test asserting that the rendered view contains a <form> element.
25
+ #
26
+ # Deprecated.
27
+ def should_render_a_form
28
+ warn "[DEPRECATION] should_render_a_form is deprecated."
29
+ should "display a form" do
30
+ assert_select "form", true, "The template doesn't contain a <form> element"
31
+ end
32
+ end
33
+
34
+ # Deprecated.
35
+ #
36
+ # Macro that creates a test asserting that the rendered view contains the selected metatags.
37
+ # Values can be string or Regexps.
38
+ # Example:
39
+ #
40
+ # should_render_page_with_metadata :description => "Description of this page", :keywords => /post/
41
+ #
42
+ # You can also use this method to test the rendered views title.
43
+ #
44
+ # Example:
45
+ # should_render_page_with_metadata :title => /index/
46
+ def should_render_page_with_metadata(options)
47
+ warn "[DEPRECATION] should_render_page_with_metadata is deprecated."
48
+ options.each do |key, value|
49
+ should "have metatag #{key}" do
50
+ if key.to_sym == :title
51
+ assert_select "title", value
52
+ else
53
+ assert_select "meta[name=?][content#{"*" if value.is_a?(Regexp)}=?]", key, value
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,16 @@
1
+ require 'shoulda'
2
+ require 'shoulda/active_record/helpers'
3
+ require 'shoulda/active_record/matchers'
4
+ require 'shoulda/active_record/assertions'
5
+ require 'shoulda/active_record/macros'
6
+
7
+ module Test # :nodoc: all
8
+ module Unit
9
+ class TestCase
10
+ include Shoulda::ActiveRecord::Helpers
11
+ include Shoulda::ActiveRecord::Matchers
12
+ include Shoulda::ActiveRecord::Assertions
13
+ extend Shoulda::ActiveRecord::Macros
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,69 @@
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
11
+
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
18
+
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
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 = 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)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,27 @@
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
+ # Helper method that determines the default error message used by Active
12
+ # Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
13
+ # introduced I18n module used for localization.
14
+ #
15
+ # default_error_message(:blank)
16
+ # default_error_message(:too_short, :count => 5)
17
+ # default_error_message(:too_long, :count => 60)
18
+ def default_error_message(key, values = {})
19
+ if Object.const_defined?(:I18n) # Rails >= 2.2
20
+ I18n.translate("activerecord.errors.messages.#{key}", values)
21
+ else # Rails <= 2.1.x
22
+ ::ActiveRecord::Errors.default_error_messages[key] % values[:count]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,512 @@
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
+ # Options:
29
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
30
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
31
+ #
32
+ # Example:
33
+ # should_validate_presence_of :name, :phone_number
34
+ #
35
+ def should_validate_presence_of(*attributes)
36
+ message = get_options!(attributes, :message)
37
+
38
+ attributes.each do |attribute|
39
+ matcher = validate_presence_of(attribute).with_message(message)
40
+ should matcher.description do
41
+ assert_accepts(matcher, subject)
42
+ end
43
+ end
44
+ end
45
+
46
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
47
+ # Requires an existing record
48
+ #
49
+ # Options:
50
+
51
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
52
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
53
+ # * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
54
+ # * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
55
+ # exact match. Ignored by non-text attributes. Default = <tt>true</tt>
56
+ #
57
+ # Examples:
58
+ # should_validate_uniqueness_of :keyword, :username
59
+ # should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
60
+ # should_validate_uniqueness_of :email, :scoped_to => :name
61
+ # should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
62
+ # should_validate_uniqueness_of :email, :case_sensitive => false
63
+ #
64
+ def should_validate_uniqueness_of(*attributes)
65
+ message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
66
+ scope = [*scope].compact
67
+ case_sensitive = true if case_sensitive.nil?
68
+
69
+ attributes.each do |attribute|
70
+ matcher = validate_uniqueness_of(attribute).
71
+ with_message(message).scoped_to(scope)
72
+ matcher = matcher.case_insensitive unless case_sensitive
73
+ should matcher.description do
74
+ assert_accepts(matcher, subject)
75
+ end
76
+ end
77
+ end
78
+
79
+ # Ensures that the attribute can be set on mass update.
80
+ #
81
+ # should_allow_mass_assignment_of :first_name, :last_name
82
+ #
83
+ def should_allow_mass_assignment_of(*attributes)
84
+ get_options!(attributes)
85
+
86
+ attributes.each do |attribute|
87
+ matcher = allow_mass_assignment_of(attribute)
88
+ should matcher.description do
89
+ assert_accepts matcher, subject
90
+ end
91
+ end
92
+ end
93
+
94
+ # Ensures that the attribute cannot be set on mass update.
95
+ #
96
+ # should_not_allow_mass_assignment_of :password, :admin_flag
97
+ #
98
+ def should_not_allow_mass_assignment_of(*attributes)
99
+ get_options!(attributes)
100
+
101
+ attributes.each do |attribute|
102
+ matcher = allow_mass_assignment_of(attribute)
103
+ should "not #{matcher.description}" do
104
+ assert_rejects matcher, subject
105
+ end
106
+ end
107
+ end
108
+
109
+ # Ensures that the attribute cannot be changed once the record has been created.
110
+ #
111
+ # should_have_readonly_attributes :password, :admin_flag
112
+ #
113
+ def should_have_readonly_attributes(*attributes)
114
+ get_options!(attributes)
115
+
116
+ attributes.each do |attribute|
117
+ matcher = have_readonly_attribute(attribute)
118
+ should matcher.description do
119
+ assert_accepts matcher, subject
120
+ end
121
+ end
122
+ end
123
+
124
+ # Ensures that the attribute cannot be set to the given values
125
+ #
126
+ # Options:
127
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
128
+ # Regexp or string. If omitted, the test will pass if there is ANY error in
129
+ # <tt>errors.on(:attribute)</tt>.
130
+ #
131
+ # Example:
132
+ # should_not_allow_values_for :isbn, "bad 1", "bad 2"
133
+ #
134
+ def should_not_allow_values_for(attribute, *bad_values)
135
+ message = get_options!(bad_values, :message)
136
+ bad_values.each do |value|
137
+ matcher = allow_value(value).for(attribute).with_message(message)
138
+ should "not #{matcher.description}" do
139
+ assert_rejects matcher, subject
140
+ end
141
+ end
142
+ end
143
+
144
+ # Ensures that the attribute can be set to the given values.
145
+ #
146
+ # Example:
147
+ # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
148
+ #
149
+ def should_allow_values_for(attribute, *good_values)
150
+ get_options!(good_values)
151
+ good_values.each do |value|
152
+ matcher = allow_value(value).for(attribute)
153
+ should matcher.description do
154
+ assert_accepts matcher, subject
155
+ end
156
+ end
157
+ end
158
+
159
+ # Ensures that the length of the attribute is in the given range
160
+ #
161
+ # Options:
162
+ # * <tt>:short_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.too_short') % range.first</tt>
164
+ # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
165
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
166
+ #
167
+ # Example:
168
+ # should_ensure_length_in_range :password, (6..20)
169
+ #
170
+ def should_ensure_length_in_range(attribute, range, opts = {})
171
+ short_message, long_message = get_options!([opts],
172
+ :short_message,
173
+ :long_message)
174
+ matcher = ensure_length_of(attribute).
175
+ is_at_least(range.first).
176
+ with_short_message(short_message).
177
+ is_at_most(range.last).
178
+ with_long_message(long_message)
179
+
180
+ should matcher.description do
181
+ assert_accepts matcher, subject
182
+ end
183
+ end
184
+
185
+ # Ensures that the length of the attribute is at least a certain length
186
+ #
187
+ # Options:
188
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
189
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
190
+ #
191
+ # Example:
192
+ # should_ensure_length_at_least :name, 3
193
+ #
194
+ def should_ensure_length_at_least(attribute, min_length, opts = {})
195
+ short_message = get_options!([opts], :short_message)
196
+
197
+ matcher = ensure_length_of(attribute).
198
+ is_at_least(min_length).
199
+ with_short_message(short_message)
200
+
201
+ should matcher.description do
202
+ assert_accepts matcher, subject
203
+ end
204
+ end
205
+
206
+ # Ensures that the length of the attribute is exactly a certain length
207
+ #
208
+ # Options:
209
+ # * <tt>: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.wrong_length') % length</tt>
211
+ #
212
+ # Example:
213
+ # should_ensure_length_is :ssn, 9
214
+ #
215
+ def should_ensure_length_is(attribute, length, opts = {})
216
+ message = get_options!([opts], :message)
217
+ matcher = ensure_length_of(attribute).
218
+ is_equal_to(length).
219
+ with_message(message)
220
+
221
+ should matcher.description do
222
+ assert_accepts matcher, subject
223
+ end
224
+ end
225
+
226
+ # Ensure that the attribute is in the range specified
227
+ #
228
+ # Options:
229
+ # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
230
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
231
+ # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
232
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
233
+ #
234
+ # Example:
235
+ # should_ensure_value_in_range :age, (0..100)
236
+ #
237
+ def should_ensure_value_in_range(attribute, range, opts = {})
238
+ message, low_message, high_message = get_options!([opts],
239
+ :message,
240
+ :low_message,
241
+ :high_message)
242
+ matcher = ensure_inclusion_of(attribute).
243
+ in_range(range).
244
+ with_message(message).
245
+ with_low_message(low_message).
246
+ with_high_message(high_message)
247
+ should matcher.description do
248
+ assert_accepts matcher, subject
249
+ end
250
+ end
251
+
252
+ # Ensure that the attribute is numeric
253
+ #
254
+ # Options:
255
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
256
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
257
+ #
258
+ # Example:
259
+ # should_validate_numericality_of :age
260
+ #
261
+ def should_validate_numericality_of(*attributes)
262
+ message = get_options!(attributes, :message)
263
+ attributes.each do |attribute|
264
+ matcher = validate_numericality_of(attribute).
265
+ with_message(message)
266
+ should matcher.description do
267
+ assert_accepts matcher, subject
268
+ end
269
+ end
270
+ end
271
+
272
+ # Ensures that the has_many relationship exists. Will also test that the
273
+ # associated table has the required columns. Works with polymorphic
274
+ # associations.
275
+ #
276
+ # Options:
277
+ # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
278
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
279
+ #
280
+ # Example:
281
+ # should_have_many :friends
282
+ # should_have_many :enemies, :through => :friends
283
+ # should_have_many :enemies, :dependent => :destroy
284
+ #
285
+ def should_have_many(*associations)
286
+ through, dependent = get_options!(associations, :through, :dependent)
287
+ associations.each do |association|
288
+ matcher = have_many(association).through(through).dependent(dependent)
289
+ should matcher.description do
290
+ assert_accepts(matcher, subject)
291
+ end
292
+ end
293
+ end
294
+
295
+ # Ensure that the has_one relationship exists. Will also test that the
296
+ # associated table has the required columns. Works with polymorphic
297
+ # associations.
298
+ #
299
+ # Options:
300
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
301
+ #
302
+ # Example:
303
+ # should_have_one :god # unless hindu
304
+ #
305
+ def should_have_one(*associations)
306
+ dependent, through = get_options!(associations, :dependent, :through)
307
+ associations.each do |association|
308
+ matcher = have_one(association).dependent(dependent).through(through)
309
+ should matcher.description do
310
+ assert_accepts(matcher, subject)
311
+ end
312
+ end
313
+ end
314
+
315
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join
316
+ # table is in place.
317
+ #
318
+ # should_have_and_belong_to_many :posts, :cars
319
+ #
320
+ def should_have_and_belong_to_many(*associations)
321
+ get_options!(associations)
322
+
323
+ associations.each do |association|
324
+ matcher = have_and_belong_to_many(association)
325
+ should matcher.description do
326
+ assert_accepts(matcher, subject)
327
+ end
328
+ end
329
+ end
330
+
331
+ # Ensure that the belongs_to relationship exists.
332
+ #
333
+ # should_belong_to :parent
334
+ #
335
+ def should_belong_to(*associations)
336
+ dependent = get_options!(associations, :dependent)
337
+ associations.each do |association|
338
+ matcher = belong_to(association).dependent(dependent)
339
+ should matcher.description do
340
+ assert_accepts(matcher, subject)
341
+ end
342
+ end
343
+ end
344
+
345
+ # Ensure that the given class methods are defined on the model.
346
+ #
347
+ # should_have_class_methods :find, :destroy
348
+ #
349
+ def should_have_class_methods(*methods)
350
+ get_options!(methods)
351
+ klass = described_type
352
+ methods.each do |method|
353
+ should "respond to class method ##{method}" do
354
+ assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
355
+ end
356
+ end
357
+ end
358
+
359
+ # Ensure that the given instance methods are defined on the model.
360
+ #
361
+ # should_have_instance_methods :email, :name, :name=
362
+ #
363
+ def should_have_instance_methods(*methods)
364
+ get_options!(methods)
365
+ klass = described_type
366
+ methods.each do |method|
367
+ should "respond to instance method ##{method}" do
368
+ assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
369
+ end
370
+ end
371
+ end
372
+
373
+ # Ensure that the given columns are defined on the models backing SQL table.
374
+ # Also aliased to should_have_db_column for readability.
375
+ # Takes the same options available in migrations:
376
+ # :type, :precision, :limit, :default, :null, and :scale
377
+ #
378
+ # Examples:
379
+ #
380
+ # should_have_db_columns :id, :email, :name, :created_at
381
+ #
382
+ # should_have_db_column :email, :type => "string", :limit => 255
383
+ # should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
384
+ # should_have_db_column :admin, :default => false, :null => false
385
+ #
386
+ def should_have_db_columns(*columns)
387
+ column_type, precision, limit, default, null, scale, sql_type =
388
+ get_options!(columns, :type, :precision, :limit,
389
+ :default, :null, :scale, :sql_type)
390
+ columns.each do |name|
391
+ matcher = have_db_column(name).
392
+ of_type(column_type).
393
+ with_options(:precision => precision, :limit => limit,
394
+ :default => default, :null => null,
395
+ :scale => scale, :sql_type => sql_type)
396
+ should matcher.description do
397
+ assert_accepts(matcher, subject)
398
+ end
399
+ end
400
+ end
401
+
402
+ alias_method :should_have_db_column, :should_have_db_columns
403
+
404
+ # Ensures that there are DB indices on the given columns or tuples of columns.
405
+ # Also aliased to should_have_db_index for readability
406
+ #
407
+ # Options:
408
+ # * <tt>:unique</tt> - whether or not the index has a unique
409
+ # constraint. Use <tt>true</tt> to explicitly test for a unique
410
+ # constraint. Use <tt>false</tt> to explicitly test for a non-unique
411
+ # constraint. Use <tt>nil</tt> if you don't care whether the index is
412
+ # unique or not. Default = <tt>nil</tt>
413
+ #
414
+ # Examples:
415
+ #
416
+ # should_have_db_indices :email, :name, [:commentable_type, :commentable_id]
417
+ # should_have_db_index :age
418
+ # should_have_db_index :ssn, :unique => true
419
+ #
420
+ def should_have_db_indices(*columns)
421
+ unique = get_options!(columns, :unique)
422
+
423
+ columns.each do |column|
424
+ matcher = have_db_index(column).unique(unique)
425
+ should matcher.description do
426
+ assert_accepts(matcher, subject)
427
+ end
428
+ end
429
+ end
430
+
431
+ alias_method :should_have_db_index, :should_have_db_indices
432
+
433
+ # Deprecated. See should_have_db_index
434
+ def should_have_index(*args)
435
+ warn "[DEPRECATION] should_have_index is deprecated. " <<
436
+ "Use should_have_db_index instead."
437
+ should_have_db_index(*args)
438
+ end
439
+
440
+ # Deprecated. See should_have_db_indices
441
+ def should_have_indices(*args)
442
+ warn "[DEPRECATION] should_have_indices is deprecated. " <<
443
+ "Use should_have_db_indices instead."
444
+ should_have_db_indices(*args)
445
+ end
446
+
447
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
448
+ #
449
+ # Options:
450
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
451
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
452
+ #
453
+ # Example:
454
+ # should_validate_acceptance_of :eula
455
+ #
456
+ def should_validate_acceptance_of(*attributes)
457
+ message = get_options!(attributes, :message)
458
+
459
+ attributes.each do |attribute|
460
+ matcher = validate_acceptance_of(attribute).with_message(message)
461
+ should matcher.description do
462
+ assert_accepts matcher, subject
463
+ end
464
+ end
465
+ end
466
+
467
+ # Deprecated.
468
+ #
469
+ # Ensures that the model has a method named scope_name that returns a NamedScope object with the
470
+ # proxy options set to the options you supply. scope_name can be either a symbol, or a method
471
+ # call which will be evaled against the model. The eval'd method call has access to all the same
472
+ # instance variables that a should statement would.
473
+ #
474
+ # Options: Any of the options that the named scope would pass on to find.
475
+ #
476
+ # Example:
477
+ #
478
+ # should_have_named_scope :visible, :conditions => {:visible => true}
479
+ #
480
+ # Passes for
481
+ #
482
+ # named_scope :visible, :conditions => {:visible => true}
483
+ #
484
+ # Or for
485
+ #
486
+ # def self.visible
487
+ # scoped(:conditions => {:visible => true})
488
+ # end
489
+ #
490
+ # You can test lambdas or methods that return ActiveRecord#scoped calls:
491
+ #
492
+ # should_have_named_scope 'recent(5)', :limit => 5
493
+ # should_have_named_scope 'recent(1)', :limit => 1
494
+ #
495
+ # Passes for
496
+ # named_scope :recent, lambda {|c| {:limit => c}}
497
+ #
498
+ # Or for
499
+ #
500
+ # def self.recent(c)
501
+ # scoped(:limit => c)
502
+ # end
503
+ #
504
+ def should_have_named_scope(scope_call, find_options = nil)
505
+ matcher = have_named_scope(scope_call).finding(find_options)
506
+ should matcher.description do
507
+ assert_accepts matcher.in_context(self), subject
508
+ end
509
+ end
510
+ end
511
+ end
512
+ end