rmm5t-shoulda 2.0.2

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