technicalpickles-shoulda 2.0.0

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