rmm5t-shoulda 2.0.2

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