francois-shoulda 2.0.5.1

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