technicalpickles-shoulda 2.0.0

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