robert-shoulda 2.10.3
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.
- data/CONTRIBUTION_GUIDELINES.rdoc +10 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +171 -0
- data/Rakefile +72 -0
- data/bin/convert_to_should_syntax +42 -0
- data/lib/shoulda/action_controller/macros.rb +240 -0
- data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +109 -0
- data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +57 -0
- data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +81 -0
- data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +74 -0
- data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +81 -0
- data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
- data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +87 -0
- data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
- data/lib/shoulda/action_controller/matchers.rb +37 -0
- data/lib/shoulda/action_controller.rb +26 -0
- data/lib/shoulda/action_mailer/assertions.rb +38 -0
- data/lib/shoulda/action_mailer.rb +10 -0
- data/lib/shoulda/action_view/macros.rb +61 -0
- data/lib/shoulda/action_view.rb +10 -0
- data/lib/shoulda/active_record/assertions.rb +69 -0
- data/lib/shoulda/active_record/helpers.rb +27 -0
- data/lib/shoulda/active_record/macros.rb +571 -0
- data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
- data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
- data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
- data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
- data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
- data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
- data/lib/shoulda/active_record/matchers/have_db_index_matcher.rb +112 -0
- data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +128 -0
- data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
- data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
- data/lib/shoulda/active_record/matchers/validate_format_of_matcher.rb +67 -0
- data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
- data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
- data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
- data/lib/shoulda/active_record/matchers/validation_matcher.rb +57 -0
- data/lib/shoulda/active_record/matchers.rb +43 -0
- data/lib/shoulda/active_record.rb +16 -0
- data/lib/shoulda/assertions.rb +71 -0
- data/lib/shoulda/autoload_macros.rb +46 -0
- data/lib/shoulda/context.rb +413 -0
- data/lib/shoulda/helpers.rb +8 -0
- data/lib/shoulda/macros.rb +133 -0
- data/lib/shoulda/private_helpers.rb +13 -0
- data/lib/shoulda/proc_extensions.rb +14 -0
- data/lib/shoulda/rails.rb +13 -0
- data/lib/shoulda/rspec.rb +11 -0
- data/lib/shoulda/tasks/list_tests.rake +29 -0
- data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
- data/lib/shoulda/tasks.rb +3 -0
- data/lib/shoulda/test_unit.rb +22 -0
- data/lib/shoulda.rb +9 -0
- data/rails/init.rb +7 -0
- data/test/README +36 -0
- data/test/fail_macros.rb +39 -0
- data/test/fixtures/addresses.yml +3 -0
- data/test/fixtures/friendships.yml +0 -0
- data/test/fixtures/posts.yml +5 -0
- data/test/fixtures/products.yml +0 -0
- data/test/fixtures/taggings.yml +0 -0
- data/test/fixtures/tags.yml +9 -0
- data/test/fixtures/users.yml +6 -0
- data/test/functional/posts_controller_test.rb +121 -0
- data/test/functional/users_controller_test.rb +19 -0
- data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
- data/test/matchers/active_record/allow_value_matcher_test.rb +64 -0
- data/test/matchers/active_record/association_matcher_test.rb +263 -0
- data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
- data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
- data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
- data/test/matchers/active_record/have_db_index_matcher_test.rb +91 -0
- data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
- data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
- data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
- data/test/matchers/active_record/validate_format_of_matcher_test.rb +39 -0
- data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
- data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
- data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
- data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
- data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
- data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
- data/test/matchers/controller/respond_with_content_type_matcher_test.rb +32 -0
- data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
- data/test/matchers/controller/route_matcher_test.rb +75 -0
- data/test/matchers/controller/set_session_matcher_test.rb +38 -0
- data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
- data/test/model_builder.rb +106 -0
- data/test/other/autoload_macro_test.rb +18 -0
- data/test/other/context_test.rb +203 -0
- data/test/other/convert_to_should_syntax_test.rb +63 -0
- data/test/other/helpers_test.rb +340 -0
- data/test/other/private_helpers_test.rb +32 -0
- data/test/other/should_test.rb +271 -0
- data/test/rails_root/app/controllers/application_controller.rb +25 -0
- data/test/rails_root/app/controllers/posts_controller.rb +87 -0
- data/test/rails_root/app/controllers/users_controller.rb +84 -0
- data/test/rails_root/app/helpers/application_helper.rb +3 -0
- data/test/rails_root/app/helpers/posts_helper.rb +2 -0
- data/test/rails_root/app/helpers/users_helper.rb +2 -0
- data/test/rails_root/app/models/address.rb +7 -0
- data/test/rails_root/app/models/flea.rb +3 -0
- data/test/rails_root/app/models/friendship.rb +4 -0
- data/test/rails_root/app/models/pets/cat.rb +7 -0
- data/test/rails_root/app/models/pets/dog.rb +10 -0
- data/test/rails_root/app/models/post.rb +12 -0
- data/test/rails_root/app/models/product.rb +12 -0
- data/test/rails_root/app/models/profile.rb +2 -0
- data/test/rails_root/app/models/registration.rb +2 -0
- data/test/rails_root/app/models/tag.rb +8 -0
- data/test/rails_root/app/models/tagging.rb +4 -0
- data/test/rails_root/app/models/treat.rb +3 -0
- data/test/rails_root/app/models/user.rb +32 -0
- data/test/rails_root/app/views/layouts/posts.rhtml +19 -0
- data/test/rails_root/app/views/layouts/users.rhtml +17 -0
- data/test/rails_root/app/views/layouts/wide.html.erb +1 -0
- data/test/rails_root/app/views/posts/edit.rhtml +27 -0
- data/test/rails_root/app/views/posts/index.rhtml +25 -0
- data/test/rails_root/app/views/posts/new.rhtml +26 -0
- data/test/rails_root/app/views/posts/show.rhtml +18 -0
- data/test/rails_root/app/views/users/edit.rhtml +22 -0
- data/test/rails_root/app/views/users/index.rhtml +22 -0
- data/test/rails_root/app/views/users/new.rhtml +21 -0
- data/test/rails_root/app/views/users/show.rhtml +13 -0
- data/test/rails_root/config/boot.rb +110 -0
- data/test/rails_root/config/database.yml +4 -0
- data/test/rails_root/config/environment.rb +18 -0
- data/test/rails_root/config/environments/test.rb +0 -0
- data/test/rails_root/config/initializers/new_rails_defaults.rb +15 -0
- data/test/rails_root/config/initializers/shoulda.rb +8 -0
- data/test/rails_root/config/routes.rb +6 -0
- data/test/rails_root/db/migrate/001_create_users.rb +19 -0
- data/test/rails_root/db/migrate/002_create_posts.rb +13 -0
- data/test/rails_root/db/migrate/003_create_taggings.rb +12 -0
- data/test/rails_root/db/migrate/004_create_tags.rb +11 -0
- data/test/rails_root/db/migrate/005_create_dogs.rb +12 -0
- data/test/rails_root/db/migrate/006_create_addresses.rb +14 -0
- data/test/rails_root/db/migrate/007_create_fleas.rb +11 -0
- data/test/rails_root/db/migrate/008_create_dogs_fleas.rb +12 -0
- data/test/rails_root/db/migrate/009_create_products.rb +17 -0
- data/test/rails_root/db/migrate/010_create_friendships.rb +14 -0
- data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
- data/test/rails_root/db/migrate/20090506203502_create_profiles.rb +12 -0
- data/test/rails_root/db/migrate/20090506203536_create_registrations.rb +14 -0
- data/test/rails_root/db/migrate/20090513104502_create_cats.rb +12 -0
- data/test/rails_root/db/schema.rb +0 -0
- data/test/rails_root/log/test.log +8963 -0
- data/test/rails_root/public/404.html +30 -0
- data/test/rails_root/public/422.html +30 -0
- data/test/rails_root/public/500.html +30 -0
- data/test/rails_root/script/console +3 -0
- data/test/rails_root/script/generate +3 -0
- data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
- data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
- data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
- data/test/rspec_test.rb +207 -0
- data/test/test_helper.rb +28 -0
- data/test/unit/address_test.rb +15 -0
- data/test/unit/cat_test.rb +7 -0
- data/test/unit/dog_test.rb +9 -0
- data/test/unit/flea_test.rb +6 -0
- data/test/unit/friendship_test.rb +6 -0
- data/test/unit/post_test.rb +19 -0
- data/test/unit/product_test.rb +23 -0
- data/test/unit/tag_test.rb +15 -0
- data/test/unit/tagging_test.rb +6 -0
- data/test/unit/user_test.rb +80 -0
- metadata +225 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
module Shoulda # :nodoc:
|
|
2
|
+
module ActiveRecord # :nodoc:
|
|
3
|
+
# = Macro test helpers for your active record models
|
|
4
|
+
#
|
|
5
|
+
# These helpers will test most of the validations and associations for your ActiveRecord models.
|
|
6
|
+
#
|
|
7
|
+
# class UserTest < Test::Unit::TestCase
|
|
8
|
+
# should_validate_presence_of :name, :phone_number
|
|
9
|
+
# should_not_allow_values_for :phone_number, "abcd", "1234"
|
|
10
|
+
# should_allow_values_for :phone_number, "(123) 456-7890"
|
|
11
|
+
#
|
|
12
|
+
# should_not_allow_mass_assignment_of :password
|
|
13
|
+
#
|
|
14
|
+
# should_have_one :profile
|
|
15
|
+
# should_have_many :dogs
|
|
16
|
+
# should_have_many :messes, :through => :dogs
|
|
17
|
+
# should_belong_to :lover
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# For all of these helpers, the last parameter may be a hash of options.
|
|
21
|
+
#
|
|
22
|
+
module Macros
|
|
23
|
+
include Helpers
|
|
24
|
+
include Matchers
|
|
25
|
+
|
|
26
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not present.
|
|
27
|
+
#
|
|
28
|
+
# Options:
|
|
29
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
30
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
|
|
31
|
+
#
|
|
32
|
+
# Example:
|
|
33
|
+
# should_validate_presence_of :name, :phone_number
|
|
34
|
+
#
|
|
35
|
+
def should_validate_presence_of(*attributes)
|
|
36
|
+
message = get_options!(attributes, :message)
|
|
37
|
+
|
|
38
|
+
attributes.each do |attribute|
|
|
39
|
+
matcher = validate_presence_of(attribute).with_message(message)
|
|
40
|
+
should matcher.description do
|
|
41
|
+
assert_accepts(matcher, subject)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not unique.
|
|
47
|
+
# Requires an existing record
|
|
48
|
+
#
|
|
49
|
+
# Options:
|
|
50
|
+
|
|
51
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
52
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
|
|
53
|
+
# * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
|
|
54
|
+
# * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
|
|
55
|
+
# exact match. Ignored by non-text attributes. Default = <tt>true</tt>
|
|
56
|
+
#
|
|
57
|
+
# Examples:
|
|
58
|
+
# should_validate_uniqueness_of :keyword, :username
|
|
59
|
+
# should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
|
|
60
|
+
# should_validate_uniqueness_of :email, :scoped_to => :name
|
|
61
|
+
# should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
|
|
62
|
+
# should_validate_uniqueness_of :email, :case_sensitive => false
|
|
63
|
+
#
|
|
64
|
+
def should_validate_uniqueness_of(*attributes)
|
|
65
|
+
message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
|
|
66
|
+
scope = [*scope].compact
|
|
67
|
+
case_sensitive = true if case_sensitive.nil?
|
|
68
|
+
|
|
69
|
+
attributes.each do |attribute|
|
|
70
|
+
matcher = validate_uniqueness_of(attribute).
|
|
71
|
+
with_message(message).scoped_to(scope)
|
|
72
|
+
matcher = matcher.case_insensitive unless case_sensitive
|
|
73
|
+
should matcher.description do
|
|
74
|
+
assert_accepts(matcher, subject)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Ensures that the attribute can be set on mass update.
|
|
80
|
+
#
|
|
81
|
+
# should_allow_mass_assignment_of :first_name, :last_name
|
|
82
|
+
#
|
|
83
|
+
def should_allow_mass_assignment_of(*attributes)
|
|
84
|
+
get_options!(attributes)
|
|
85
|
+
|
|
86
|
+
attributes.each do |attribute|
|
|
87
|
+
matcher = allow_mass_assignment_of(attribute)
|
|
88
|
+
should matcher.description do
|
|
89
|
+
assert_accepts matcher, subject
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Ensures that the attribute cannot be set on mass update.
|
|
95
|
+
#
|
|
96
|
+
# should_not_allow_mass_assignment_of :password, :admin_flag
|
|
97
|
+
#
|
|
98
|
+
def should_not_allow_mass_assignment_of(*attributes)
|
|
99
|
+
get_options!(attributes)
|
|
100
|
+
|
|
101
|
+
attributes.each do |attribute|
|
|
102
|
+
matcher = allow_mass_assignment_of(attribute)
|
|
103
|
+
should "not #{matcher.description}" do
|
|
104
|
+
assert_rejects matcher, subject
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Ensures that the attribute cannot be changed once the record has been created.
|
|
110
|
+
#
|
|
111
|
+
# should_have_readonly_attributes :password, :admin_flag
|
|
112
|
+
#
|
|
113
|
+
def should_have_readonly_attributes(*attributes)
|
|
114
|
+
get_options!(attributes)
|
|
115
|
+
|
|
116
|
+
attributes.each do |attribute|
|
|
117
|
+
matcher = have_readonly_attribute(attribute)
|
|
118
|
+
should matcher.description do
|
|
119
|
+
assert_accepts matcher, subject
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Ensures that the attribute cannot be set to the given values
|
|
125
|
+
#
|
|
126
|
+
# Options:
|
|
127
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
128
|
+
# Regexp or string. If omitted, the test will pass if there is ANY error in
|
|
129
|
+
# <tt>errors.on(:attribute)</tt>.
|
|
130
|
+
#
|
|
131
|
+
# Example:
|
|
132
|
+
# should_not_allow_values_for :isbn, "bad 1", "bad 2"
|
|
133
|
+
#
|
|
134
|
+
def should_not_allow_values_for(attribute, *bad_values)
|
|
135
|
+
message = get_options!(bad_values, :message)
|
|
136
|
+
bad_values.each do |value|
|
|
137
|
+
matcher = allow_value(value).for(attribute).with_message(message)
|
|
138
|
+
should "not #{matcher.description}" do
|
|
139
|
+
assert_rejects matcher, subject
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Ensures that the attribute can be set to the given values.
|
|
145
|
+
#
|
|
146
|
+
# Example:
|
|
147
|
+
# should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
|
|
148
|
+
#
|
|
149
|
+
def should_allow_values_for(attribute, *good_values)
|
|
150
|
+
get_options!(good_values)
|
|
151
|
+
good_values.each do |value|
|
|
152
|
+
matcher = allow_value(value).for(attribute)
|
|
153
|
+
should matcher.description do
|
|
154
|
+
assert_accepts matcher, subject
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Ensures that the length of the attribute is in the given range
|
|
160
|
+
#
|
|
161
|
+
# Options:
|
|
162
|
+
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
163
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
|
|
164
|
+
# * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
165
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
|
|
166
|
+
#
|
|
167
|
+
# Example:
|
|
168
|
+
# should_ensure_length_in_range :password, (6..20)
|
|
169
|
+
#
|
|
170
|
+
def should_ensure_length_in_range(attribute, range, opts = {})
|
|
171
|
+
short_message, long_message = get_options!([opts],
|
|
172
|
+
:short_message,
|
|
173
|
+
:long_message)
|
|
174
|
+
matcher = ensure_length_of(attribute).
|
|
175
|
+
is_at_least(range.first).
|
|
176
|
+
with_short_message(short_message).
|
|
177
|
+
is_at_most(range.last).
|
|
178
|
+
with_long_message(long_message)
|
|
179
|
+
|
|
180
|
+
should matcher.description do
|
|
181
|
+
assert_accepts matcher, subject
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Ensures that the length of the attribute is at least a certain length
|
|
186
|
+
#
|
|
187
|
+
# Options:
|
|
188
|
+
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
189
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
|
|
190
|
+
#
|
|
191
|
+
# Example:
|
|
192
|
+
# should_ensure_length_at_least :name, 3
|
|
193
|
+
#
|
|
194
|
+
def should_ensure_length_at_least(attribute, min_length, opts = {})
|
|
195
|
+
short_message = get_options!([opts], :short_message)
|
|
196
|
+
|
|
197
|
+
matcher = ensure_length_of(attribute).
|
|
198
|
+
is_at_least(min_length).
|
|
199
|
+
with_short_message(short_message)
|
|
200
|
+
|
|
201
|
+
should matcher.description do
|
|
202
|
+
assert_accepts matcher, subject
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Ensures that the length of the attribute is exactly a certain length
|
|
207
|
+
#
|
|
208
|
+
# Options:
|
|
209
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
210
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
|
|
211
|
+
#
|
|
212
|
+
# Example:
|
|
213
|
+
# should_ensure_length_is :ssn, 9
|
|
214
|
+
#
|
|
215
|
+
def should_ensure_length_is(attribute, length, opts = {})
|
|
216
|
+
message = get_options!([opts], :message)
|
|
217
|
+
matcher = ensure_length_of(attribute).
|
|
218
|
+
is_equal_to(length).
|
|
219
|
+
with_message(message)
|
|
220
|
+
|
|
221
|
+
should matcher.description do
|
|
222
|
+
assert_accepts matcher, subject
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Ensure that the attribute is in the range specified
|
|
227
|
+
#
|
|
228
|
+
# Options:
|
|
229
|
+
# * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
230
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
|
|
231
|
+
# * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
232
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
|
|
233
|
+
#
|
|
234
|
+
# Example:
|
|
235
|
+
# should_ensure_value_in_range :age, (0..100)
|
|
236
|
+
#
|
|
237
|
+
def should_ensure_value_in_range(attribute, range, opts = {})
|
|
238
|
+
message, low_message, high_message = get_options!([opts],
|
|
239
|
+
:message,
|
|
240
|
+
:low_message,
|
|
241
|
+
:high_message)
|
|
242
|
+
matcher = ensure_inclusion_of(attribute).
|
|
243
|
+
in_range(range).
|
|
244
|
+
with_message(message).
|
|
245
|
+
with_low_message(low_message).
|
|
246
|
+
with_high_message(high_message)
|
|
247
|
+
should matcher.description do
|
|
248
|
+
assert_accepts matcher, subject
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Ensure that the attribute is numeric
|
|
253
|
+
#
|
|
254
|
+
# Options:
|
|
255
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
256
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
|
|
257
|
+
#
|
|
258
|
+
# Example:
|
|
259
|
+
# should_validate_numericality_of :age
|
|
260
|
+
#
|
|
261
|
+
def should_validate_numericality_of(*attributes)
|
|
262
|
+
message = get_options!(attributes, :message)
|
|
263
|
+
attributes.each do |attribute|
|
|
264
|
+
matcher = validate_numericality_of(attribute).
|
|
265
|
+
with_message(message)
|
|
266
|
+
should matcher.description do
|
|
267
|
+
assert_accepts matcher, subject
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# Ensures that the has_many relationship exists. Will also test that the
|
|
274
|
+
# associated table has the required columns. Works with polymorphic
|
|
275
|
+
# associations.
|
|
276
|
+
#
|
|
277
|
+
# Options:
|
|
278
|
+
# * <tt>:through</tt> - association name for <tt>has_many :through</tt>
|
|
279
|
+
# * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
|
|
280
|
+
#
|
|
281
|
+
# Example:
|
|
282
|
+
# should_have_many :friends
|
|
283
|
+
# should_have_many :enemies, :through => :friends
|
|
284
|
+
# should_have_many :enemies, :dependent => :destroy
|
|
285
|
+
#
|
|
286
|
+
def should_have_many(*associations)
|
|
287
|
+
through, dependent = get_options!(associations, :through, :dependent)
|
|
288
|
+
associations.each do |association|
|
|
289
|
+
matcher = have_many(association).through(through).dependent(dependent)
|
|
290
|
+
should matcher.description do
|
|
291
|
+
assert_accepts(matcher, subject)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# TODO: Create a new matcher thus, that the following disjunction become a
|
|
297
|
+
# conjunction.
|
|
298
|
+
# Ensures that no has_many relationship exists or (sic, see Todo above) that no
|
|
299
|
+
# associated table has the required columns. Works with polymorphic
|
|
300
|
+
# associations.
|
|
301
|
+
#
|
|
302
|
+
# Options:
|
|
303
|
+
# * <tt>:through</tt> - association name for <tt>has_many :through</tt>
|
|
304
|
+
# * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
|
|
305
|
+
#
|
|
306
|
+
# Example:
|
|
307
|
+
# should_not_have_many :friends
|
|
308
|
+
# should_not_have_many :enemies, :through => :friends
|
|
309
|
+
# should_not_have_many :enemies, :dependent => :destroy
|
|
310
|
+
#
|
|
311
|
+
def should_not_have_many(*associations)
|
|
312
|
+
through, dependent = get_options!(associations, :through, :dependent)
|
|
313
|
+
associations.each do |association|
|
|
314
|
+
matcher = have_many(association).through(through).dependent(dependent)
|
|
315
|
+
should "not #{matcher.description}" do
|
|
316
|
+
assert_rejects(matcher, subject)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
# Ensure that the has_one relationship exists. Will also test that the
|
|
321
|
+
# associated table has the required columns. Works with polymorphic
|
|
322
|
+
# associations.
|
|
323
|
+
#
|
|
324
|
+
# Options:
|
|
325
|
+
# * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
|
|
326
|
+
#
|
|
327
|
+
# Example:
|
|
328
|
+
# should_have_one :god # unless hindu
|
|
329
|
+
#
|
|
330
|
+
def should_have_one(*associations)
|
|
331
|
+
dependent, through = get_options!(associations, :dependent, :through)
|
|
332
|
+
associations.each do |association|
|
|
333
|
+
matcher = have_one(association).dependent(dependent).through(through)
|
|
334
|
+
should matcher.description do
|
|
335
|
+
assert_accepts(matcher, subject)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Ensures that the has_and_belongs_to_many relationship exists, and that the join
|
|
341
|
+
# table is in place.
|
|
342
|
+
#
|
|
343
|
+
# should_have_and_belong_to_many :posts, :cars
|
|
344
|
+
#
|
|
345
|
+
def should_have_and_belong_to_many(*associations)
|
|
346
|
+
get_options!(associations)
|
|
347
|
+
|
|
348
|
+
associations.each do |association|
|
|
349
|
+
matcher = have_and_belong_to_many(association)
|
|
350
|
+
should matcher.description do
|
|
351
|
+
assert_accepts(matcher, subject)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# Ensure that the belongs_to relationship exists.
|
|
358
|
+
#
|
|
359
|
+
# should_belong_to :parent
|
|
360
|
+
#
|
|
361
|
+
def should_belong_to(*associations)
|
|
362
|
+
dependent = get_options!(associations, :dependent)
|
|
363
|
+
associations.each do |association|
|
|
364
|
+
matcher = belong_to(association).dependent(dependent)
|
|
365
|
+
should matcher.description do
|
|
366
|
+
assert_accepts(matcher, subject)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Ensure that the belongs_to relationship does not exist.
|
|
372
|
+
#
|
|
373
|
+
# should_not_belong_to :parent
|
|
374
|
+
#
|
|
375
|
+
def should_not_belong_to(*associations)
|
|
376
|
+
dependent = get_options!(associations, :dependent)
|
|
377
|
+
associations.each do |association|
|
|
378
|
+
matcher = belong_to(association).dependent(dependent)
|
|
379
|
+
should "not #{matcher.description}" do
|
|
380
|
+
assert_rejects(matcher, subject)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
# Ensure that the given class methods are defined on the model.
|
|
385
|
+
#
|
|
386
|
+
# should_have_class_methods :find, :destroy
|
|
387
|
+
#
|
|
388
|
+
def should_have_class_methods(*methods)
|
|
389
|
+
get_options!(methods)
|
|
390
|
+
klass = described_type
|
|
391
|
+
methods.each do |method|
|
|
392
|
+
should "respond to class method ##{method}" do
|
|
393
|
+
assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Ensure that the given instance methods are defined on the model.
|
|
399
|
+
#
|
|
400
|
+
# should_have_instance_methods :email, :name, :name=
|
|
401
|
+
#
|
|
402
|
+
def should_have_instance_methods(*methods)
|
|
403
|
+
get_options!(methods)
|
|
404
|
+
klass = described_type
|
|
405
|
+
methods.each do |method|
|
|
406
|
+
should "respond to instance method ##{method}" do
|
|
407
|
+
assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# Ensure that the given columns are defined on the models backing SQL table.
|
|
414
|
+
# Also aliased to should_have_db_column for readability.
|
|
415
|
+
# Takes the same options available in migrations:
|
|
416
|
+
# :type, :precision, :limit, :default, :null, and :scale
|
|
417
|
+
#
|
|
418
|
+
# Examples:
|
|
419
|
+
#
|
|
420
|
+
# should_have_db_columns :id, :email, :name, :created_at
|
|
421
|
+
#
|
|
422
|
+
# should_have_db_column :email, :type => "string", :limit => 255
|
|
423
|
+
# should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
|
|
424
|
+
# should_have_db_column :admin, :default => false, :null => false
|
|
425
|
+
#
|
|
426
|
+
def should_have_db_columns(*columns)
|
|
427
|
+
column_type, precision, limit, default, null, scale, sql_type =
|
|
428
|
+
get_options!(columns, :type, :precision, :limit,
|
|
429
|
+
:default, :null, :scale, :sql_type)
|
|
430
|
+
columns.each do |name|
|
|
431
|
+
matcher = have_db_column(name).
|
|
432
|
+
of_type(column_type).
|
|
433
|
+
with_options(:precision => precision, :limit => limit,
|
|
434
|
+
:default => default, :null => null,
|
|
435
|
+
:scale => scale, :sql_type => sql_type)
|
|
436
|
+
should matcher.description do
|
|
437
|
+
assert_accepts(matcher, subject)
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
alias_method :should_have_db_column, :should_have_db_columns
|
|
443
|
+
|
|
444
|
+
# Ensure that the given columns are not defined on the models backing SQL table.
|
|
445
|
+
# Also aliased to should_not_have_db_column for readability.
|
|
446
|
+
# Does not take any options as the total absence of the column is tested.
|
|
447
|
+
#
|
|
448
|
+
# Examples:
|
|
449
|
+
#
|
|
450
|
+
# should_not_have_db_columns :id, :email, :name, :created_at
|
|
451
|
+
#
|
|
452
|
+
def should_not_have_db_columns(*columns)
|
|
453
|
+
columns.each do |name|
|
|
454
|
+
matcher = have_db_column(name)
|
|
455
|
+
should "not #{matcher.description}" do
|
|
456
|
+
assert_rejects(matcher, subject)
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
alias_method :should_not_have_db_column, :should_not_have_db_columns
|
|
462
|
+
|
|
463
|
+
# Ensures that there are DB indices on the given columns or tuples of columns.
|
|
464
|
+
# Also aliased to should_have_db_index for readability
|
|
465
|
+
#
|
|
466
|
+
# Options:
|
|
467
|
+
# * <tt>:unique</tt> - whether or not the index has a unique
|
|
468
|
+
# constraint. Use <tt>true</tt> to explicitly test for a unique
|
|
469
|
+
# constraint. Use <tt>false</tt> to explicitly test for a non-unique
|
|
470
|
+
# constraint. Use <tt>nil</tt> if you don't care whether the index is
|
|
471
|
+
# unique or not. Default = <tt>nil</tt>
|
|
472
|
+
#
|
|
473
|
+
# Examples:
|
|
474
|
+
#
|
|
475
|
+
# should_have_db_indices :email, :name, [:commentable_type, :commentable_id]
|
|
476
|
+
# should_have_db_index :age
|
|
477
|
+
# should_have_db_index :ssn, :unique => true
|
|
478
|
+
#
|
|
479
|
+
def should_have_db_indices(*columns)
|
|
480
|
+
unique = get_options!(columns, :unique)
|
|
481
|
+
|
|
482
|
+
columns.each do |column|
|
|
483
|
+
matcher = have_db_index(column).unique(unique)
|
|
484
|
+
should matcher.description do
|
|
485
|
+
assert_accepts(matcher, subject)
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
alias_method :should_have_db_index, :should_have_db_indices
|
|
491
|
+
|
|
492
|
+
# Deprecated. See should_have_db_index
|
|
493
|
+
def should_have_index(*args)
|
|
494
|
+
warn "[DEPRECATION] should_have_index is deprecated. " <<
|
|
495
|
+
"Use should_have_db_index instead."
|
|
496
|
+
should_have_db_index(*args)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Deprecated. See should_have_db_indices
|
|
500
|
+
def should_have_indices(*args)
|
|
501
|
+
warn "[DEPRECATION] should_have_indices is deprecated. " <<
|
|
502
|
+
"Use should_have_db_indices instead."
|
|
503
|
+
should_have_db_indices(*args)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not accepted.
|
|
507
|
+
#
|
|
508
|
+
# Options:
|
|
509
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
|
510
|
+
# Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
|
|
511
|
+
#
|
|
512
|
+
# Example:
|
|
513
|
+
# should_validate_acceptance_of :eula
|
|
514
|
+
#
|
|
515
|
+
def should_validate_acceptance_of(*attributes)
|
|
516
|
+
message = get_options!(attributes, :message)
|
|
517
|
+
|
|
518
|
+
attributes.each do |attribute|
|
|
519
|
+
matcher = validate_acceptance_of(attribute).with_message(message)
|
|
520
|
+
should matcher.description do
|
|
521
|
+
assert_accepts matcher, subject
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# Deprecated.
|
|
527
|
+
#
|
|
528
|
+
# Ensures that the model has a method named scope_name that returns a NamedScope object with the
|
|
529
|
+
# proxy options set to the options you supply. scope_name can be either a symbol, or a method
|
|
530
|
+
# call which will be evaled against the model. The eval'd method call has access to all the same
|
|
531
|
+
# instance variables that a should statement would.
|
|
532
|
+
#
|
|
533
|
+
# Options: Any of the options that the named scope would pass on to find.
|
|
534
|
+
#
|
|
535
|
+
# Example:
|
|
536
|
+
#
|
|
537
|
+
# should_have_named_scope :visible, :conditions => {:visible => true}
|
|
538
|
+
#
|
|
539
|
+
# Passes for
|
|
540
|
+
#
|
|
541
|
+
# named_scope :visible, :conditions => {:visible => true}
|
|
542
|
+
#
|
|
543
|
+
# Or for
|
|
544
|
+
#
|
|
545
|
+
# def self.visible
|
|
546
|
+
# scoped(:conditions => {:visible => true})
|
|
547
|
+
# end
|
|
548
|
+
#
|
|
549
|
+
# You can test lambdas or methods that return ActiveRecord#scoped calls:
|
|
550
|
+
#
|
|
551
|
+
# should_have_named_scope 'recent(5)', :limit => 5
|
|
552
|
+
# should_have_named_scope 'recent(1)', :limit => 1
|
|
553
|
+
#
|
|
554
|
+
# Passes for
|
|
555
|
+
# named_scope :recent, lambda {|c| {:limit => c}}
|
|
556
|
+
#
|
|
557
|
+
# Or for
|
|
558
|
+
#
|
|
559
|
+
# def self.recent(c)
|
|
560
|
+
# scoped(:limit => c)
|
|
561
|
+
# end
|
|
562
|
+
#
|
|
563
|
+
def should_have_named_scope(scope_call, find_options = nil)
|
|
564
|
+
matcher = have_named_scope(scope_call).finding(find_options)
|
|
565
|
+
should matcher.description do
|
|
566
|
+
assert_accepts matcher.in_context(self), subject
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Shoulda # :nodoc:
|
|
2
|
+
module ActiveRecord # :nodoc:
|
|
3
|
+
module Matchers
|
|
4
|
+
|
|
5
|
+
# Ensures that the attribute can be set on mass update.
|
|
6
|
+
#
|
|
7
|
+
# it { should_not allow_mass_assignment_of(:password) }
|
|
8
|
+
# it { should allow_mass_assignment_of(:first_name) }
|
|
9
|
+
#
|
|
10
|
+
def allow_mass_assignment_of(value)
|
|
11
|
+
AllowMassAssignmentOfMatcher.new(value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class AllowMassAssignmentOfMatcher # :nodoc:
|
|
15
|
+
|
|
16
|
+
def initialize(attribute)
|
|
17
|
+
@attribute = attribute.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def matches?(subject)
|
|
21
|
+
@subject = subject
|
|
22
|
+
if attr_mass_assignable?
|
|
23
|
+
if whitelisting?
|
|
24
|
+
@failure_message = "#{@attribute} was made accessible"
|
|
25
|
+
else
|
|
26
|
+
if protected_attributes.empty?
|
|
27
|
+
@failure_message = "no attributes were protected"
|
|
28
|
+
else
|
|
29
|
+
@failure_message = "#{class_name} is protecting " <<
|
|
30
|
+
"#{protected_attributes.to_a.to_sentence}, " <<
|
|
31
|
+
"but not #{@attribute}."
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
true
|
|
35
|
+
else
|
|
36
|
+
if whitelisting?
|
|
37
|
+
@negative_failure_message =
|
|
38
|
+
"Expected #{@attribute} to be accessible"
|
|
39
|
+
else
|
|
40
|
+
@negative_failure_message =
|
|
41
|
+
"Did not expect #{@attribute} to be protected"
|
|
42
|
+
end
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :failure_message, :negative_failure_message
|
|
48
|
+
|
|
49
|
+
def description
|
|
50
|
+
"allow mass assignment of #{@attribute}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def protected_attributes
|
|
56
|
+
@protected_attributes ||= (@subject.class.protected_attributes || [])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def accessible_attributes
|
|
60
|
+
@accessible_attributes ||= (@subject.class.accessible_attributes || [])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def whitelisting?
|
|
64
|
+
!accessible_attributes.empty?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def attr_mass_assignable?
|
|
68
|
+
if whitelisting?
|
|
69
|
+
accessible_attributes.include?(@attribute)
|
|
70
|
+
else
|
|
71
|
+
!protected_attributes.include?(@attribute)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def class_name
|
|
76
|
+
@subject.class.name
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|