tpitale-shoulda 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
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