shoulda 2.0.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 (104) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +12 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +132 -0
  4. data/Rakefile +72 -0
  5. data/bin/convert_to_should_syntax +42 -0
  6. data/lib/shoulda.rb +16 -0
  7. data/lib/shoulda/action_mailer.rb +10 -0
  8. data/lib/shoulda/action_mailer/assertions.rb +39 -0
  9. data/lib/shoulda/active_record.rb +12 -0
  10. data/lib/shoulda/active_record/assertions.rb +85 -0
  11. data/lib/shoulda/active_record/macros.rb +703 -0
  12. data/lib/shoulda/assertions.rb +45 -0
  13. data/lib/shoulda/context.rb +309 -0
  14. data/lib/shoulda/controller.rb +30 -0
  15. data/lib/shoulda/controller/formats/html.rb +201 -0
  16. data/lib/shoulda/controller/formats/xml.rb +170 -0
  17. data/lib/shoulda/controller/helpers.rb +64 -0
  18. data/lib/shoulda/controller/macros.rb +287 -0
  19. data/lib/shoulda/controller/resource_options.rb +236 -0
  20. data/lib/shoulda/controller/routing.rb +0 -0
  21. data/lib/shoulda/controller/routing/macros.rb +0 -0
  22. data/lib/shoulda/helpers.rb +10 -0
  23. data/lib/shoulda/macros.rb +80 -0
  24. data/lib/shoulda/private_helpers.rb +22 -0
  25. data/lib/shoulda/proc_extensions.rb +14 -0
  26. data/lib/shoulda/rails.rb +19 -0
  27. data/lib/shoulda/tasks.rb +3 -0
  28. data/lib/shoulda/tasks/list_tests.rake +24 -0
  29. data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
  30. data/test/README +36 -0
  31. data/test/fixtures/addresses.yml +3 -0
  32. data/test/fixtures/friendships.yml +0 -0
  33. data/test/fixtures/posts.yml +5 -0
  34. data/test/fixtures/products.yml +0 -0
  35. data/test/fixtures/taggings.yml +0 -0
  36. data/test/fixtures/tags.yml +9 -0
  37. data/test/fixtures/users.yml +6 -0
  38. data/test/functional/posts_controller_test.rb +104 -0
  39. data/test/functional/users_controller_test.rb +36 -0
  40. data/test/other/context_test.rb +145 -0
  41. data/test/other/convert_to_should_syntax_test.rb +63 -0
  42. data/test/other/helpers_test.rb +183 -0
  43. data/test/other/private_helpers_test.rb +34 -0
  44. data/test/other/should_test.rb +266 -0
  45. data/test/rails_root/app/controllers/application.rb +25 -0
  46. data/test/rails_root/app/controllers/posts_controller.rb +85 -0
  47. data/test/rails_root/app/controllers/users_controller.rb +81 -0
  48. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  49. data/test/rails_root/app/helpers/posts_helper.rb +2 -0
  50. data/test/rails_root/app/helpers/users_helper.rb +2 -0
  51. data/test/rails_root/app/models/address.rb +7 -0
  52. data/test/rails_root/app/models/dog.rb +5 -0
  53. data/test/rails_root/app/models/flea.rb +3 -0
  54. data/test/rails_root/app/models/friendship.rb +4 -0
  55. data/test/rails_root/app/models/post.rb +12 -0
  56. data/test/rails_root/app/models/product.rb +12 -0
  57. data/test/rails_root/app/models/tag.rb +8 -0
  58. data/test/rails_root/app/models/tagging.rb +4 -0
  59. data/test/rails_root/app/models/user.rb +28 -0
  60. data/test/rails_root/app/views/layouts/posts.rhtml +17 -0
  61. data/test/rails_root/app/views/layouts/users.rhtml +17 -0
  62. data/test/rails_root/app/views/posts/edit.rhtml +27 -0
  63. data/test/rails_root/app/views/posts/index.rhtml +25 -0
  64. data/test/rails_root/app/views/posts/new.rhtml +26 -0
  65. data/test/rails_root/app/views/posts/show.rhtml +18 -0
  66. data/test/rails_root/app/views/users/edit.rhtml +22 -0
  67. data/test/rails_root/app/views/users/index.rhtml +22 -0
  68. data/test/rails_root/app/views/users/new.rhtml +21 -0
  69. data/test/rails_root/app/views/users/show.rhtml +13 -0
  70. data/test/rails_root/config/boot.rb +109 -0
  71. data/test/rails_root/config/database.yml +4 -0
  72. data/test/rails_root/config/environment.rb +14 -0
  73. data/test/rails_root/config/environments/sqlite3.rb +0 -0
  74. data/test/rails_root/config/initializers/new_rails_defaults.rb +15 -0
  75. data/test/rails_root/config/initializers/shoulda.rb +8 -0
  76. data/test/rails_root/config/routes.rb +6 -0
  77. data/test/rails_root/db/migrate/001_create_users.rb +18 -0
  78. data/test/rails_root/db/migrate/002_create_posts.rb +13 -0
  79. data/test/rails_root/db/migrate/003_create_taggings.rb +12 -0
  80. data/test/rails_root/db/migrate/004_create_tags.rb +11 -0
  81. data/test/rails_root/db/migrate/005_create_dogs.rb +12 -0
  82. data/test/rails_root/db/migrate/006_create_addresses.rb +14 -0
  83. data/test/rails_root/db/migrate/007_create_fleas.rb +11 -0
  84. data/test/rails_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  85. data/test/rails_root/db/migrate/009_create_products.rb +17 -0
  86. data/test/rails_root/db/migrate/010_create_friendships.rb +14 -0
  87. data/test/rails_root/db/schema.rb +0 -0
  88. data/test/rails_root/log/sqlite3.log +0 -0
  89. data/test/rails_root/public/404.html +30 -0
  90. data/test/rails_root/public/422.html +30 -0
  91. data/test/rails_root/public/500.html +30 -0
  92. data/test/rails_root/script/console +3 -0
  93. data/test/rails_root/script/generate +3 -0
  94. data/test/test_helper.rb +33 -0
  95. data/test/unit/address_test.rb +10 -0
  96. data/test/unit/dog_test.rb +7 -0
  97. data/test/unit/flea_test.rb +6 -0
  98. data/test/unit/friendship_test.rb +6 -0
  99. data/test/unit/post_test.rb +15 -0
  100. data/test/unit/product_test.rb +27 -0
  101. data/test/unit/tag_test.rb +14 -0
  102. data/test/unit/tagging_test.rb +6 -0
  103. data/test/unit/user_test.rb +52 -0
  104. metadata +170 -0
@@ -0,0 +1,703 @@
1
+ module ThoughtBot # :nodoc:
2
+ module Shoulda # :nodoc:
3
+ module ActiveRecord # :nodoc:
4
+ DEFAULT_ERROR_MESSAGES =
5
+ if Object.const_defined?(:I18n)
6
+ I18n.translate('activerecord.errors.messages')
7
+ else
8
+ ::ActiveRecord::Errors.default_error_messages
9
+ end
10
+
11
+ # = Macro test helpers for your active record models
12
+ #
13
+ # These helpers will test most of the validations and associations for your ActiveRecord models.
14
+ #
15
+ # class UserTest < Test::Unit::TestCase
16
+ # should_require_attributes :name, :phone_number
17
+ # should_not_allow_values_for :phone_number, "abcd", "1234"
18
+ # should_allow_values_for :phone_number, "(123) 456-7890"
19
+ #
20
+ # should_protect_attributes :password
21
+ #
22
+ # should_have_one :profile
23
+ # should_have_many :dogs
24
+ # should_have_many :messes, :through => :dogs
25
+ # should_belong_to :lover
26
+ # end
27
+ #
28
+ # For all of these helpers, the last parameter may be a hash of options.
29
+ #
30
+ module Macros
31
+ # Loads all fixture files (<tt>test/fixtures/*.yml</tt>)
32
+ # Deprecated: Use <tt>fixtures :all</tt> instead
33
+ def load_all_fixtures
34
+ warn "[DEPRECATION] load_all_fixtures is deprecated. Use `fixtures :all` instead."
35
+ fixtures :all
36
+ end
37
+
38
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
39
+ #
40
+ # If an instance variable has been created in the setup named after the
41
+ # model being tested, then this method will use that. Otherwise, it will
42
+ # create a new instance to test against.
43
+ #
44
+ # Options:
45
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
46
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:blank]</tt>
47
+ #
48
+ # Example:
49
+ # should_require_attributes :name, :phone_number
50
+ #
51
+ def should_require_attributes(*attributes)
52
+ message = get_options!(attributes, :message)
53
+ message ||= DEFAULT_ERROR_MESSAGES[:blank]
54
+ klass = model_class
55
+
56
+ attributes.each do |attribute|
57
+ should "require #{attribute} to be set" do
58
+ assert_bad_value(klass, attribute, nil, message)
59
+ end
60
+ end
61
+ end
62
+
63
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
64
+ # Requires an existing record
65
+ #
66
+ # Options:
67
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
68
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:taken]</tt>
69
+ # * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
70
+ #
71
+ # Examples:
72
+ # should_require_unique_attributes :keyword, :username
73
+ # should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
74
+ # should_require_unique_attributes :email, :scoped_to => :name
75
+ # should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
76
+ #
77
+ def should_require_unique_attributes(*attributes)
78
+ message, scope = get_options!(attributes, :message, :scoped_to)
79
+ scope = [*scope].compact
80
+ message ||= DEFAULT_ERROR_MESSAGES[:taken]
81
+
82
+ klass = model_class
83
+ attributes.each do |attribute|
84
+ attribute = attribute.to_sym
85
+ should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" unless scope.blank?}" do
86
+ assert existing = klass.find(:first), "Can't find first #{klass}"
87
+ object = klass.new
88
+ existing_value = existing.send(attribute)
89
+
90
+ if !scope.blank?
91
+ scope.each do |s|
92
+ assert_respond_to object, :"#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute."
93
+ object.send("#{s}=", existing.send(s))
94
+ end
95
+ end
96
+ assert_bad_value(object, attribute, existing_value, message)
97
+
98
+ # Now test that the object is valid when changing the scoped attribute
99
+ # TODO: There is a chance that we could change the scoped field
100
+ # to a value that's already taken. An alternative implementation
101
+ # could actually find all values for scope and create a unique
102
+ # one.
103
+ if !scope.blank?
104
+ scope.each do |s|
105
+ # Assume the scope is a foreign key if the field is nil
106
+ object.send("#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next)
107
+ assert_good_value(object, attribute, existing_value, message)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ # Ensures that the attribute cannot be set on mass update.
115
+ #
116
+ # should_protect_attributes :password, :admin_flag
117
+ #
118
+ def should_protect_attributes(*attributes)
119
+ get_options!(attributes)
120
+ klass = model_class
121
+
122
+ attributes.each do |attribute|
123
+ attribute = attribute.to_sym
124
+ should "protect #{attribute} from mass updates" do
125
+ protected = klass.protected_attributes || []
126
+ accessible = klass.accessible_attributes || []
127
+
128
+ assert protected.include?(attribute.to_s) ||
129
+ (!accessible.empty? && !accessible.include?(attribute.to_s)),
130
+ (accessible.empty? ?
131
+ "#{klass} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." :
132
+ "#{klass} has made #{attribute} accessible")
133
+ end
134
+ end
135
+ end
136
+
137
+ # Ensures that the attribute cannot be changed once the record has been created.
138
+ #
139
+ # should_have_readonly_attributes :password, :admin_flag
140
+ #
141
+ def should_have_readonly_attributes(*attributes)
142
+ get_options!(attributes)
143
+ klass = model_class
144
+
145
+ attributes.each do |attribute|
146
+ attribute = attribute.to_sym
147
+ should "make #{attribute} read-only" do
148
+ readonly = klass.readonly_attributes || []
149
+
150
+ assert readonly.include?(attribute.to_s),
151
+ (readonly.empty? ?
152
+ "#{klass} attribute #{attribute} is not read-only" :
153
+ "#{klass} is making #{readonly.to_a.to_sentence} read-only, but not #{attribute}.")
154
+ end
155
+ end
156
+ end
157
+
158
+ # Ensures that the attribute cannot be set to the given values
159
+ #
160
+ # If an instance variable has been created in the setup named after the
161
+ # model being tested, then this method will use that. Otherwise, it will
162
+ # create a new instance to test against.
163
+ #
164
+ # Options:
165
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
166
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:invalid]</tt>
167
+ #
168
+ # Example:
169
+ # should_not_allow_values_for :isbn, "bad 1", "bad 2"
170
+ #
171
+ def should_not_allow_values_for(attribute, *bad_values)
172
+ message = get_options!(bad_values, :message)
173
+ message ||= DEFAULT_ERROR_MESSAGES[:invalid]
174
+ klass = model_class
175
+ bad_values.each do |v|
176
+ should "not allow #{attribute} to be set to #{v.inspect}" do
177
+ assert_bad_value(klass, attribute, v, message)
178
+ end
179
+ end
180
+ end
181
+
182
+ # Ensures that the attribute can be set to the given values.
183
+ #
184
+ # If an instance variable has been created in the setup named after the
185
+ # model being tested, then this method will use that. Otherwise, it will
186
+ # create a new instance to test against.
187
+ #
188
+ # Example:
189
+ # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
190
+ #
191
+ def should_allow_values_for(attribute, *good_values)
192
+ get_options!(good_values)
193
+ klass = model_class
194
+ good_values.each do |v|
195
+ should "allow #{attribute} to be set to #{v.inspect}" do
196
+ assert_good_value(klass, attribute, v)
197
+ end
198
+ end
199
+ end
200
+
201
+ # Ensures that the length of the attribute is in the given range
202
+ #
203
+ # If an instance variable has been created in the setup named after the
204
+ # model being tested, then this method will use that. Otherwise, it will
205
+ # create a new instance to test against.
206
+ #
207
+ # Options:
208
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
209
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:too_short] % range.first</tt>
210
+ # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
211
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:too_long] % range.last</tt>
212
+ #
213
+ # Example:
214
+ # should_ensure_length_in_range :password, (6..20)
215
+ #
216
+ def should_ensure_length_in_range(attribute, range, opts = {})
217
+ short_message, long_message = get_options!([opts], :short_message, :long_message)
218
+ short_message ||= DEFAULT_ERROR_MESSAGES[:too_short] % range.first
219
+ long_message ||= DEFAULT_ERROR_MESSAGES[:too_long] % range.last
220
+
221
+ klass = model_class
222
+ min_length = range.first
223
+ max_length = range.last
224
+ same_length = (min_length == max_length)
225
+
226
+ if min_length > 0
227
+ should "not allow #{attribute} to be less than #{min_length} chars long" do
228
+ min_value = "x" * (min_length - 1)
229
+ assert_bad_value(klass, attribute, min_value, short_message)
230
+ end
231
+ end
232
+
233
+ if min_length > 0
234
+ should "allow #{attribute} to be exactly #{min_length} chars long" do
235
+ min_value = "x" * min_length
236
+ assert_good_value(klass, attribute, min_value, short_message)
237
+ end
238
+ end
239
+
240
+ should "not allow #{attribute} to be more than #{max_length} chars long" do
241
+ max_value = "x" * (max_length + 1)
242
+ assert_bad_value(klass, attribute, max_value, long_message)
243
+ end
244
+
245
+ unless same_length
246
+ should "allow #{attribute} to be exactly #{max_length} chars long" do
247
+ max_value = "x" * max_length
248
+ assert_good_value(klass, attribute, max_value, long_message)
249
+ end
250
+ end
251
+ end
252
+
253
+ # Ensures that the length of the attribute is at least a certain length
254
+ #
255
+ # If an instance variable has been created in the setup named after the
256
+ # model being tested, then this method will use that. Otherwise, it will
257
+ # create a new instance to test against.
258
+ #
259
+ # Options:
260
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
261
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:too_short] % min_length</tt>
262
+ #
263
+ # Example:
264
+ # should_ensure_length_at_least :name, 3
265
+ #
266
+ def should_ensure_length_at_least(attribute, min_length, opts = {})
267
+ short_message = get_options!([opts], :short_message)
268
+ short_message ||= DEFAULT_ERROR_MESSAGES[:too_short] % min_length
269
+
270
+ klass = model_class
271
+
272
+ if min_length > 0
273
+ min_value = "x" * (min_length - 1)
274
+ should "not allow #{attribute} to be less than #{min_length} chars long" do
275
+ assert_bad_value(klass, attribute, min_value, short_message)
276
+ end
277
+ end
278
+ should "allow #{attribute} to be at least #{min_length} chars long" do
279
+ valid_value = "x" * (min_length)
280
+ assert_good_value(klass, attribute, valid_value, short_message)
281
+ end
282
+ end
283
+
284
+ # Ensures that the length of the attribute is exactly a certain length
285
+ #
286
+ # If an instance variable has been created in the setup named after the
287
+ # model being tested, then this method will use that. Otherwise, it will
288
+ # create a new instance to test against.
289
+ #
290
+ # Options:
291
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
292
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:wrong_length] % length</tt>
293
+ #
294
+ # Example:
295
+ # should_ensure_length_is :ssn, 9
296
+ #
297
+ def should_ensure_length_is(attribute, length, opts = {})
298
+ message = get_options!([opts], :message)
299
+ message ||= DEFAULT_ERROR_MESSAGES[:wrong_length] % length
300
+
301
+ klass = model_class
302
+
303
+ should "not allow #{attribute} to be less than #{length} chars long" do
304
+ min_value = "x" * (length - 1)
305
+ assert_bad_value(klass, attribute, min_value, message)
306
+ end
307
+
308
+ should "not allow #{attribute} to be greater than #{length} chars long" do
309
+ max_value = "x" * (length + 1)
310
+ assert_bad_value(klass, attribute, max_value, message)
311
+ end
312
+
313
+ should "allow #{attribute} to be #{length} chars long" do
314
+ valid_value = "x" * (length)
315
+ assert_good_value(klass, attribute, valid_value, message)
316
+ end
317
+ end
318
+
319
+ # Ensure that the attribute is in the range specified
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>:low_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')[:inclusion]</tt>
328
+ # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
329
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:inclusion]</tt>
330
+ #
331
+ # Example:
332
+ # should_ensure_value_in_range :age, (0..100)
333
+ #
334
+ def should_ensure_value_in_range(attribute, range, opts = {})
335
+ low_message, high_message = get_options!([opts], :low_message, :high_message)
336
+ low_message ||= DEFAULT_ERROR_MESSAGES[:inclusion]
337
+ high_message ||= DEFAULT_ERROR_MESSAGES[:inclusion]
338
+
339
+ klass = model_class
340
+ min = range.first
341
+ max = range.last
342
+
343
+ should "not allow #{attribute} to be less than #{min}" do
344
+ v = min - 1
345
+ assert_bad_value(klass, attribute, v, low_message)
346
+ end
347
+
348
+ should "allow #{attribute} to be #{min}" do
349
+ v = min
350
+ assert_good_value(klass, attribute, v, low_message)
351
+ end
352
+
353
+ should "not allow #{attribute} to be more than #{max}" do
354
+ v = max + 1
355
+ assert_bad_value(klass, attribute, v, high_message)
356
+ end
357
+
358
+ should "allow #{attribute} to be #{max}" do
359
+ v = max
360
+ assert_good_value(klass, attribute, v, high_message)
361
+ end
362
+ end
363
+
364
+ # Ensure that the attribute is numeric
365
+ #
366
+ # If an instance variable has been created in the setup named after the
367
+ # model being tested, then this method will use that. Otherwise, it will
368
+ # create a new instance to test against.
369
+ #
370
+ # Options:
371
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
372
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:not_a_number]</tt>
373
+ #
374
+ # Example:
375
+ # should_only_allow_numeric_values_for :age
376
+ #
377
+ def should_only_allow_numeric_values_for(*attributes)
378
+ message = get_options!(attributes, :message)
379
+ message ||= DEFAULT_ERROR_MESSAGES[:not_a_number]
380
+ klass = model_class
381
+ attributes.each do |attribute|
382
+ attribute = attribute.to_sym
383
+ should "only allow numeric values for #{attribute}" do
384
+ assert_bad_value(klass, attribute, "abcd", message)
385
+ end
386
+ end
387
+ end
388
+
389
+ # Ensures that the has_many relationship exists. Will also test that the
390
+ # associated table has the required columns. Works with polymorphic
391
+ # associations.
392
+ #
393
+ # Options:
394
+ # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
395
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
396
+ #
397
+ # Example:
398
+ # should_have_many :friends
399
+ # should_have_many :enemies, :through => :friends
400
+ # should_have_many :enemies, :dependent => :destroy
401
+ #
402
+ def should_have_many(*associations)
403
+ through, dependent = get_options!(associations, :through, :dependent)
404
+ klass = model_class
405
+ associations.each do |association|
406
+ name = "have many #{association}"
407
+ name += " through #{through}" if through
408
+ name += " dependent => #{dependent}" if dependent
409
+ should name do
410
+ reflection = klass.reflect_on_association(association)
411
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
412
+ assert_equal :has_many, reflection.macro
413
+
414
+ if through
415
+ through_reflection = klass.reflect_on_association(through)
416
+ assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
417
+ assert_equal(through, reflection.options[:through])
418
+ end
419
+
420
+ if dependent
421
+ assert_equal dependent.to_s,
422
+ reflection.options[:dependent].to_s,
423
+ "#{association} should have #{dependent} dependency"
424
+ end
425
+
426
+ # Check for the existence of the foreign key on the other table
427
+ unless reflection.options[:through]
428
+ if reflection.options[:foreign_key]
429
+ fk = reflection.options[:foreign_key]
430
+ elsif reflection.options[:as]
431
+ fk = reflection.options[:as].to_s.foreign_key
432
+ else
433
+ fk = reflection.primary_key_name
434
+ end
435
+
436
+ associated_klass_name = (reflection.options[:class_name] || association.to_s.classify)
437
+ associated_klass = associated_klass_name.constantize
438
+
439
+ assert associated_klass.column_names.include?(fk.to_s),
440
+ "#{associated_klass.name} does not have a #{fk} foreign key."
441
+ end
442
+ end
443
+ end
444
+ end
445
+
446
+ # Ensure that the has_one relationship exists. Will also test that the
447
+ # associated table has the required columns. Works with polymorphic
448
+ # associations.
449
+ #
450
+ # Options:
451
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
452
+ #
453
+ # Example:
454
+ # should_have_one :god # unless hindu
455
+ #
456
+ def should_have_one(*associations)
457
+ dependent = get_options!(associations, :dependent)
458
+ klass = model_class
459
+ associations.each do |association|
460
+ name = "have one #{association}"
461
+ name += " dependent => #{dependent}" if dependent
462
+ should name do
463
+ reflection = klass.reflect_on_association(association)
464
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
465
+ assert_equal :has_one, reflection.macro
466
+
467
+ associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
468
+
469
+ if reflection.options[:foreign_key]
470
+ fk = reflection.options[:foreign_key]
471
+ elsif reflection.options[:as]
472
+ fk = reflection.options[:as].to_s.foreign_key
473
+ fk_type = fk.gsub(/_id$/, '_type')
474
+ assert associated_klass.column_names.include?(fk_type),
475
+ "#{associated_klass.name} does not have a #{fk_type} column."
476
+ else
477
+ fk = klass.name.foreign_key
478
+ end
479
+ assert associated_klass.column_names.include?(fk.to_s),
480
+ "#{associated_klass.name} does not have a #{fk} foreign key."
481
+
482
+ if dependent
483
+ assert_equal dependent.to_s,
484
+ reflection.options[:dependent].to_s,
485
+ "#{association} should have #{dependent} dependency"
486
+ end
487
+ end
488
+ end
489
+ end
490
+
491
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join
492
+ # table is in place.
493
+ #
494
+ # should_have_and_belong_to_many :posts, :cars
495
+ #
496
+ def should_have_and_belong_to_many(*associations)
497
+ get_options!(associations)
498
+ klass = model_class
499
+
500
+ associations.each do |association|
501
+ should "should have and belong to many #{association}" do
502
+ reflection = klass.reflect_on_association(association)
503
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
504
+ assert_equal :has_and_belongs_to_many, reflection.macro
505
+ table = reflection.options[:join_table]
506
+ assert ::ActiveRecord::Base.connection.tables.include?(table), "table #{table} doesn't exist"
507
+ end
508
+ end
509
+ end
510
+
511
+ # Ensure that the belongs_to relationship exists.
512
+ #
513
+ # should_belong_to :parent
514
+ #
515
+ def should_belong_to(*associations)
516
+ get_options!(associations)
517
+ klass = model_class
518
+ associations.each do |association|
519
+ should "belong_to #{association}" do
520
+ reflection = klass.reflect_on_association(association)
521
+ assert reflection, "#{klass.name} does not have any relationship to #{association}"
522
+ assert_equal :belongs_to, reflection.macro
523
+
524
+ unless reflection.options[:polymorphic]
525
+ associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
526
+ fk = reflection.options[:foreign_key] || reflection.primary_key_name
527
+ assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key."
528
+ end
529
+ end
530
+ end
531
+ end
532
+
533
+ # Ensure that the given class methods are defined on the model.
534
+ #
535
+ # should_have_class_methods :find, :destroy
536
+ #
537
+ def should_have_class_methods(*methods)
538
+ get_options!(methods)
539
+ klass = model_class
540
+ methods.each do |method|
541
+ should "respond to class method ##{method}" do
542
+ assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
543
+ end
544
+ end
545
+ end
546
+
547
+ # Ensure that the given instance methods are defined on the model.
548
+ #
549
+ # should_have_instance_methods :email, :name, :name=
550
+ #
551
+ def should_have_instance_methods(*methods)
552
+ get_options!(methods)
553
+ klass = model_class
554
+ methods.each do |method|
555
+ should "respond to instance method ##{method}" do
556
+ assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
557
+ end
558
+ end
559
+ end
560
+
561
+ # Ensure that the given columns are defined on the models backing SQL table.
562
+ #
563
+ # should_have_db_columns :id, :email, :name, :created_at
564
+ #
565
+ def should_have_db_columns(*columns)
566
+ column_type = get_options!(columns, :type)
567
+ klass = model_class
568
+ columns.each do |name|
569
+ test_name = "have column #{name}"
570
+ test_name += " of type #{column_type}" if column_type
571
+ should test_name do
572
+ column = klass.columns.detect {|c| c.name == name.to_s }
573
+ assert column, "#{klass.name} does not have column #{name}"
574
+ end
575
+ end
576
+ end
577
+
578
+ # Ensure that the given column is defined on the models backing SQL table. The options are the same as
579
+ # the instance variables defined on the column definition: :precision, :limit, :default, :null,
580
+ # :primary, :type, :scale, and :sql_type.
581
+ #
582
+ # should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
583
+ # :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
584
+ #
585
+ def should_have_db_column(name, opts = {})
586
+ klass = model_class
587
+ test_name = "have column named :#{name}"
588
+ test_name += " with options " + opts.inspect unless opts.empty?
589
+ should test_name do
590
+ column = klass.columns.detect {|c| c.name == name.to_s }
591
+ assert column, "#{klass.name} does not have column #{name}"
592
+ opts.each do |k, v|
593
+ assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}"
594
+ end
595
+ end
596
+ end
597
+
598
+ # Ensures that there are DB indices on the given columns or tuples of columns.
599
+ # Also aliased to should_have_index for readability
600
+ #
601
+ # should_have_indices :email, :name, [:commentable_type, :commentable_id]
602
+ # should_have_index :age
603
+ #
604
+ def should_have_indices(*columns)
605
+ table = model_class.name.tableize
606
+ indices = ::ActiveRecord::Base.connection.indexes(table).map(&:columns)
607
+
608
+ columns.each do |column|
609
+ should "have index on #{table} for #{column.inspect}" do
610
+ columns = [column].flatten.map(&:to_s)
611
+ assert_contains(indices, columns)
612
+ end
613
+ end
614
+ end
615
+
616
+ alias_method :should_have_index, :should_have_indices
617
+
618
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
619
+ #
620
+ # If an instance variable has been created in the setup named after the
621
+ # model being tested, then this method will use that. Otherwise, it will
622
+ # create a new instance to test against.
623
+ #
624
+ # Options:
625
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
626
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages')[:accepted]</tt>
627
+ #
628
+ # Example:
629
+ # should_require_acceptance_of :eula
630
+ #
631
+ def should_require_acceptance_of(*attributes)
632
+ message = get_options!(attributes, :message)
633
+ message ||= DEFAULT_ERROR_MESSAGES[:accepted]
634
+ klass = model_class
635
+
636
+ attributes.each do |attribute|
637
+ should "require #{attribute} to be accepted" do
638
+ assert_bad_value(klass, attribute, false, message)
639
+ end
640
+ end
641
+ end
642
+
643
+ # Ensures that the model has a method named scope_name that returns a NamedScope object with the
644
+ # proxy options set to the options you supply. scope_name can be either a symbol, or a method
645
+ # call which will be evaled against the model. The eval'd method call has access to all the same
646
+ # instance variables that a should statement would.
647
+ #
648
+ # Options: Any of the options that the named scope would pass on to find.
649
+ #
650
+ # Example:
651
+ #
652
+ # should_have_named_scope :visible, :conditions => {:visible => true}
653
+ #
654
+ # Passes for
655
+ #
656
+ # named_scope :visible, :conditions => {:visible => true}
657
+ #
658
+ # Or for
659
+ #
660
+ # def self.visible
661
+ # scoped(:conditions => {:visible => true})
662
+ # end
663
+ #
664
+ # You can test lambdas or methods that return ActiveRecord#scoped calls:
665
+ #
666
+ # should_have_named_scope 'recent(5)', :limit => 5
667
+ # should_have_named_scope 'recent(1)', :limit => 1
668
+ #
669
+ # Passes for
670
+ # named_scope :recent, lambda {|c| {:limit => c}}
671
+ #
672
+ # Or for
673
+ #
674
+ # def self.recent(c)
675
+ # scoped(:limit => c)
676
+ # end
677
+ #
678
+ def should_have_named_scope(scope_call, *args)
679
+ klass = model_class
680
+ scope_opts = args.extract_options!
681
+ scope_call = scope_call.to_s
682
+
683
+ context scope_call do
684
+ setup do
685
+ @scope = eval("#{klass}.#{scope_call}")
686
+ end
687
+
688
+ should "return a scope object" do
689
+ assert_equal ::ActiveRecord::NamedScope::Scope, @scope.class
690
+ end
691
+
692
+ unless scope_opts.empty?
693
+ should "scope itself to #{scope_opts.inspect}" do
694
+ assert_equal scope_opts, @scope.proxy_options
695
+ end
696
+ end
697
+ end
698
+ end
699
+
700
+ end
701
+ end
702
+ end
703
+ end