shoulda-matchers 3.0.1 → 3.1.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -3
  4. data/CONTRIBUTING.md +60 -28
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +15 -12
  7. data/NEWS.md +111 -0
  8. data/README.md +94 -6
  9. data/Rakefile +10 -8
  10. data/custom_plan.rb +88 -0
  11. data/gemfiles/4.0.0.gemfile +1 -0
  12. data/gemfiles/4.0.0.gemfile.lock +21 -18
  13. data/gemfiles/4.0.1.gemfile +1 -0
  14. data/gemfiles/4.0.1.gemfile.lock +21 -18
  15. data/gemfiles/4.1.gemfile +1 -0
  16. data/gemfiles/4.1.gemfile.lock +21 -18
  17. data/gemfiles/4.2.gemfile +1 -0
  18. data/gemfiles/4.2.gemfile.lock +24 -21
  19. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
  20. data/lib/shoulda/matchers/active_model.rb +10 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
  22. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  23. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  24. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
  25. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  26. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  27. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  28. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  29. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  30. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
  31. data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
  32. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
  36. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
  37. data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
  38. data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  39. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  40. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
  41. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
  42. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
  43. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
  44. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
  45. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
  46. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
  47. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
  48. data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
  49. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
  50. data/lib/shoulda/matchers/active_model/validator.rb +71 -52
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
  53. data/lib/shoulda/matchers/util.rb +43 -0
  54. data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
  55. data/lib/shoulda/matchers/version.rb +1 -1
  56. data/script/update_gem_in_all_appraisals +1 -1
  57. data/script/update_gems_in_all_appraisals +1 -1
  58. data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
  59. data/spec/acceptance/rails_integration_spec.rb +6 -2
  60. data/spec/spec_helper.rb +1 -3
  61. data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
  62. data/spec/support/tests/current_bundle.rb +21 -7
  63. data/spec/support/unit/active_record/create_table.rb +54 -0
  64. data/spec/support/unit/attribute.rb +47 -0
  65. data/spec/support/unit/capture.rb +6 -0
  66. data/spec/support/unit/change_value.rb +111 -0
  67. data/spec/support/unit/create_model_arguments/basic.rb +135 -0
  68. data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
  69. data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
  70. data/spec/support/unit/helpers/active_record_versions.rb +1 -1
  71. data/spec/support/unit/helpers/class_builder.rb +61 -47
  72. data/spec/support/unit/helpers/database_helpers.rb +5 -3
  73. data/spec/support/unit/helpers/model_builder.rb +77 -97
  74. data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
  75. data/spec/support/unit/load_environment.rb +12 -0
  76. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
  77. data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
  78. data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
  79. data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
  80. data/spec/support/unit/model_creators.rb +19 -0
  81. data/spec/support/unit/model_creators/active_model.rb +39 -0
  82. data/spec/support/unit/model_creators/active_record.rb +43 -0
  83. data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
  84. data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
  85. data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
  86. data/spec/support/unit/model_creators/basic.rb +97 -0
  87. data/spec/support/unit/rails_application.rb +1 -1
  88. data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
  89. data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
  90. data/spec/support/unit/validation_matcher_scenario.rb +62 -0
  91. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
  92. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
  93. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
  94. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
  95. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
  96. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
  97. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
  98. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
  99. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
  100. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
  101. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
  102. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
  103. data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
  104. data/spec/unit_spec_helper.rb +10 -22
  105. data/zeus.json +11 -0
  106. metadata +64 -23
  107. data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
  108. data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
  109. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
  110. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
  111. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
  112. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -21,6 +21,41 @@ describe Shoulda::Matchers::ActiveModel::ValidateLengthOfMatcher, type: :model d
21
21
  expect(validating_length(minimum: 4)).
22
22
  to validate_length_of(:attr).is_at_least(4).with_short_message(nil)
23
23
  end
24
+
25
+ it_supports(
26
+ 'ignoring_interference_by_writer',
27
+ tests: {
28
+ accept_if_qualified_but_changing_value_does_not_interfere: {
29
+ changing_values_with: :upcase,
30
+ },
31
+ reject_if_qualified_but_changing_value_interferes: {
32
+ model_name: 'Example',
33
+ attribute_name: :attr,
34
+ changing_values_with: :add_character,
35
+ expected_message: <<-MESSAGE.strip
36
+ Example did not properly validate that the length of :attr is at least
37
+ 4.
38
+ After setting :attr to ‹"xxx"› -- which was read back as ‹"xxxa"› --
39
+ the matcher expected the Example to be invalid, but it was valid
40
+ instead.
41
+
42
+ As indicated in the message above, :attr seems to be changing certain
43
+ values as they are set, and this could have something to do with why
44
+ this test is failing. If you've overridden the writer method for this
45
+ attribute, then you may need to change it to make this test pass, or
46
+ do something else entirely.
47
+ MESSAGE
48
+ }
49
+ }
50
+ ) do
51
+ def validation_matcher_scenario_args
52
+ super.deep_merge(validation_options: { minimum: 4 })
53
+ end
54
+
55
+ def configure_validation_matcher(matcher)
56
+ matcher.is_at_least(4)
57
+ end
58
+ end
24
59
  end
25
60
 
26
61
  context 'an attribute with a minimum length validation of 0' do
@@ -50,6 +85,40 @@ describe Shoulda::Matchers::ActiveModel::ValidateLengthOfMatcher, type: :model d
50
85
  expect(validating_length(maximum: 4)).
51
86
  to validate_length_of(:attr).is_at_most(4).with_long_message(nil)
52
87
  end
88
+
89
+ it_supports(
90
+ 'ignoring_interference_by_writer',
91
+ tests: {
92
+ accept_if_qualified_but_changing_value_does_not_interfere: {
93
+ changing_values_with: :upcase,
94
+ },
95
+ reject_if_qualified_but_changing_value_interferes: {
96
+ model_name: 'Example',
97
+ attribute_name: :attr,
98
+ changing_values_with: :remove_character,
99
+ expected_message: <<-MESSAGE.strip
100
+ Example did not properly validate that the length of :attr is at most 4.
101
+ After setting :attr to ‹"xxxxx"› -- which was read back as ‹"xxxx"› --
102
+ the matcher expected the Example to be invalid, but it was valid
103
+ instead.
104
+
105
+ As indicated in the message above, :attr seems to be changing certain
106
+ values as they are set, and this could have something to do with why
107
+ this test is failing. If you've overridden the writer method for this
108
+ attribute, then you may need to change it to make this test pass, or
109
+ do something else entirely.
110
+ MESSAGE
111
+ }
112
+ }
113
+ ) do
114
+ def validation_matcher_scenario_args
115
+ super.deep_merge(validation_options: { maximum: 4 })
116
+ end
117
+
118
+ def configure_validation_matcher(matcher)
119
+ matcher.is_at_most(4)
120
+ end
121
+ end
53
122
  end
54
123
 
55
124
  context 'an attribute with a required exact length' do
@@ -72,6 +141,40 @@ describe Shoulda::Matchers::ActiveModel::ValidateLengthOfMatcher, type: :model d
72
141
  expect(validating_length(is: 4)).
73
142
  to validate_length_of(:attr).is_equal_to(4).with_message(nil)
74
143
  end
144
+
145
+ it_supports(
146
+ 'ignoring_interference_by_writer',
147
+ tests: {
148
+ accept_if_qualified_but_changing_value_does_not_interfere: {
149
+ changing_values_with: :upcase,
150
+ },
151
+ reject_if_qualified_but_changing_value_interferes: {
152
+ model_name: 'Example',
153
+ attribute_name: :attr,
154
+ changing_values_with: :add_character,
155
+ expected_message: <<-MESSAGE.strip
156
+ Example did not properly validate that the length of :attr is 4.
157
+ After setting :attr to ‹"xxx"› -- which was read back as ‹"xxxa"› --
158
+ the matcher expected the Example to be invalid, but it was valid
159
+ instead.
160
+
161
+ As indicated in the message above, :attr seems to be changing certain
162
+ values as they are set, and this could have something to do with why
163
+ this test is failing. If you've overridden the writer method for this
164
+ attribute, then you may need to change it to make this test pass, or
165
+ do something else entirely.
166
+ MESSAGE
167
+ }
168
+ }
169
+ ) do
170
+ def validation_matcher_scenario_args
171
+ super.deep_merge(validation_options: { is: 4 })
172
+ end
173
+
174
+ def configure_validation_matcher(matcher)
175
+ matcher.is_equal_to(4)
176
+ end
177
+ end
75
178
  end
76
179
 
77
180
  context 'an attribute with a required exact length and another validation' do
@@ -161,9 +264,25 @@ describe Shoulda::Matchers::ActiveModel::ValidateLengthOfMatcher, type: :model d
161
264
  end
162
265
  end
163
266
 
267
+ def define_model_validating_length(options = {})
268
+ options = options.dup
269
+ attribute_name = options.delete(:attribute_name) { :attr }
270
+
271
+ define_model(:example, attribute_name => :string) do |model|
272
+ model.validates_length_of(attribute_name, options)
273
+ end
274
+ end
275
+
164
276
  def validating_length(options = {})
165
- define_model(:example, attr: :string) do
166
- validates_length_of :attr, options
167
- end.new
277
+ define_model_validating_length(options).new
278
+ end
279
+
280
+ alias_method :build_record_validating_length, :validating_length
281
+
282
+ def validation_matcher_scenario_args
283
+ super.deep_merge(
284
+ matcher_name: :validate_length_of,
285
+ model_creator: :active_model
286
+ )
168
287
  end
169
288
  end
@@ -127,39 +127,65 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
127
127
  expect(record).to validate_numericality
128
128
  end
129
129
 
130
- context 'when the column is an integer column' do
131
- it 'raises an IneffectiveTestError' do
132
- record = build_record_validating_numericality(
133
- column_type: :integer
134
- )
135
- assertion = -> { expect(record).to validate_numericality }
130
+ it_supports(
131
+ 'ignoring_interference_by_writer',
132
+ tests: {
133
+ accept_if_qualified_but_changing_value_does_not_interfere: {
134
+ changing_values_with: :next_value,
135
+ },
136
+ reject_if_qualified_but_changing_value_interferes: {
137
+ model_name: 'Example',
138
+ attribute_name: :attr,
139
+ changing_values_with: :numeric_value,
140
+ expected_message: <<-MESSAGE.strip
141
+ Example did not properly validate that :attr looks like a number.
142
+ After setting :attr to ‹"abcd"› -- which was read back as ‹"1"› -- the
143
+ matcher expected the Example to be invalid, but it was valid instead.
144
+
145
+ As indicated in the message above, :attr seems to be changing certain
146
+ values as they are set, and this could have something to do with why
147
+ this test is failing. If you've overridden the writer method for this
148
+ attribute, then you may need to change it to make this test pass, or
149
+ do something else entirely.
150
+ MESSAGE
151
+ }
152
+ }
153
+ )
136
154
 
137
- expect(&assertion).
138
- to raise_error(described_class::IneffectiveTestError)
155
+ context 'when the attribute is a virtual attribute in an ActiveRecord model' do
156
+ it 'accepts' do
157
+ record = build_record_validating_numericality_of_virtual_attribute
158
+ expect(record).to validate_numericality
139
159
  end
140
160
  end
141
161
 
142
- context 'when the column is a float column' do
143
- it 'raises an IneffectiveTestError' do
144
- record = build_record_validating_numericality(
145
- column_type: :float
146
- )
147
- assertion = -> { expect(record).to validate_numericality }
162
+ context 'when the column is an integer column' do
163
+ it 'accepts (and does not raise an AttributeChangedValueError)' do
164
+ record = build_record_validating_numericality(column_type: :integer)
165
+ expect(record).to validate_numericality
166
+ end
167
+ end
148
168
 
149
- expect(&assertion).
150
- to raise_error(described_class::IneffectiveTestError)
169
+ context 'when the column is a float column' do
170
+ it 'accepts (and does not raise an AttributeChangedValueError)' do
171
+ record = build_record_validating_numericality(column_type: :float)
172
+ expect(record).to validate_numericality
151
173
  end
152
174
  end
153
175
 
154
176
  context 'when the column is a decimal column' do
155
- it 'raises an IneffectiveTestError' do
156
- record = build_record_validating_numericality(
157
- column_type: :decimal,
158
- )
159
- assertion = -> { expect(record).to validate_numericality }
177
+ it 'accepts (and does not raise an AttributeChangedValueError)' do
178
+ record = build_record_validating_numericality(column_type: :decimal)
179
+ expect(record).to validate_numericality
180
+ end
181
+ end
160
182
 
161
- expect(&assertion).
162
- to raise_error(described_class::IneffectiveTestError)
183
+ if database_supports_money_columns?
184
+ context 'when the column is a money column' do
185
+ it 'accepts (and does not raise an AttributeChangedValueError)' do
186
+ record = build_record_validating_numericality(column_type: :money)
187
+ expect(record).to validate_numericality
188
+ end
163
189
  end
164
190
  end
165
191
  end
@@ -167,10 +193,16 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
167
193
  context 'and not validating anything' do
168
194
  it 'rejects since it does not disallow non-numbers' do
169
195
  record = build_record_validating_nothing
196
+
170
197
  assertion = -> { expect(record).to validate_numericality }
171
- expect(&assertion).to fail_with_message_including(
172
- 'Expected errors to include "is not a number"'
173
- )
198
+
199
+ message = <<-MESSAGE
200
+ Example did not properly validate that :attr looks like a number.
201
+ After setting :attr to ‹"abcd"›, the matcher expected the Example to
202
+ be invalid, but it was valid instead.
203
+ MESSAGE
204
+
205
+ expect(&assertion).to fail_with_message(message)
174
206
  end
175
207
  end
176
208
  end
@@ -181,17 +213,65 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
181
213
  record = build_record_validating_numericality(allow_nil: true)
182
214
  expect(record).to validate_numericality.allow_nil
183
215
  end
216
+
217
+ it_supports(
218
+ 'ignoring_interference_by_writer',
219
+ tests: {
220
+ accept_if_qualified_but_changing_value_does_not_interfere: {
221
+ changing_values_with: :next_value_or_numeric_value,
222
+ },
223
+ reject_if_qualified_but_changing_value_interferes: {
224
+ model_name: 'Example',
225
+ attribute_name: :attr,
226
+ changing_values_with: :next_value_or_non_numeric_value,
227
+ expected_message: <<-MESSAGE.strip
228
+ Example did not properly validate that :attr looks like a number, but
229
+ only if it is not nil.
230
+ In checking that Example allows :attr to be ‹nil›, after setting :attr
231
+ to ‹nil› -- which was read back as ‹"a"› -- the matcher expected the
232
+ Example to be valid, but it was invalid instead, producing these
233
+ validation errors:
234
+
235
+ * attr: ["is not a number"]
236
+
237
+ As indicated in the message above, :attr seems to be changing certain
238
+ values as they are set, and this could have something to do with why
239
+ this test is failing. If you've overridden the writer method for this
240
+ attribute, then you may need to change it to make this test pass, or
241
+ do something else entirely.
242
+ MESSAGE
243
+ }
244
+ }
245
+ ) do
246
+ def validation_matcher_scenario_args
247
+ super.deep_merge(validation_options: { allow_nil: true })
248
+ end
249
+
250
+ def configure_validation_matcher(matcher)
251
+ matcher.allow_nil
252
+ end
253
+ end
184
254
  end
185
255
 
186
256
  context 'and not validating with allow_nil' do
187
257
  it 'rejects since it tries to treat nil as a number' do
188
258
  record = build_record_validating_numericality
259
+
189
260
  assertion = lambda do
190
261
  expect(record).to validate_numericality.allow_nil
191
262
  end
192
- expect(&assertion).to fail_with_message_including(
193
- %[Did not expect errors to include "is not a number" when #{attribute_name} is set to nil]
194
- )
263
+
264
+ message = <<-MESSAGE
265
+ Example did not properly validate that :attr looks like a number, but
266
+ only if it is not nil.
267
+ In checking that Example allows :attr to be ‹nil›, after setting :attr
268
+ to ‹nil›, the matcher expected the Example to be valid, but it was
269
+ invalid instead, producing these validation errors:
270
+
271
+ * attr: ["is not a number"]
272
+ MESSAGE
273
+
274
+ expect(&assertion).to fail_with_message(message)
195
275
  end
196
276
  end
197
277
  end
@@ -202,17 +282,56 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
202
282
  record = build_record_validating_numericality(only_integer: true)
203
283
  expect(record).to validate_numericality.only_integer
204
284
  end
285
+
286
+ it_supports(
287
+ 'ignoring_interference_by_writer',
288
+ tests: {
289
+ accept_if_qualified_but_changing_value_does_not_interfere: {
290
+ changing_values_with: :next_value,
291
+ },
292
+ reject_if_qualified_but_changing_value_interferes: {
293
+ model_name: 'Example',
294
+ attribute_name: :attr,
295
+ changing_values_with: :numeric_value,
296
+ expected_message: <<-MESSAGE.strip
297
+ Example did not properly validate that :attr looks like an integer.
298
+ After setting :attr to ‹"0.1"› -- which was read back as ‹"1"› -- the
299
+ matcher expected the Example to be invalid, but it was valid instead.
300
+
301
+ As indicated in the message above, :attr seems to be changing certain
302
+ values as they are set, and this could have something to do with why
303
+ this test is failing. If you've overridden the writer method for this
304
+ attribute, then you may need to change it to make this test pass, or
305
+ do something else entirely.
306
+ MESSAGE
307
+ }
308
+ }
309
+ ) do
310
+ def validation_matcher_scenario_args
311
+ super.deep_merge(validation_options: { only_integer: true })
312
+ end
313
+
314
+ def configure_validation_matcher(matcher)
315
+ matcher.only_integer
316
+ end
317
+ end
205
318
  end
206
319
 
207
320
  context 'and not validating with only_integer' do
208
321
  it 'rejects since it does not disallow non-integers' do
209
322
  record = build_record_validating_numericality
323
+
210
324
  assertion = lambda do
211
325
  expect(record).to validate_numericality.only_integer
212
326
  end
213
- expect(&assertion).to fail_with_message_including(
214
- 'Expected errors to include "must be an integer"'
215
- )
327
+
328
+ message = <<-MESSAGE
329
+ Example did not properly validate that :attr looks like an integer.
330
+ After setting :attr to ‹"0.1"›, the matcher expected the Example to be
331
+ invalid, but it was valid instead.
332
+ MESSAGE
333
+
334
+ expect(&assertion).to fail_with_message(message)
216
335
  end
217
336
  end
218
337
  end
@@ -224,6 +343,48 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
224
343
  expect(record).to validate_numericality.odd
225
344
  end
226
345
 
346
+ it_supports(
347
+ 'ignoring_interference_by_writer',
348
+ tests: {
349
+ accept_if_qualified_but_changing_value_does_not_interfere: {
350
+ changing_values_with: :next_next_value,
351
+ },
352
+ reject_if_qualified_but_changing_value_interferes: {
353
+ model_name: 'Example',
354
+ attribute_name: :attr,
355
+ changing_values_with: :next_value,
356
+ expected_message: <<-MESSAGE.strip
357
+ Example did not properly validate that :attr looks like an odd number.
358
+ After setting :attr to ‹"2"› -- which was read back as ‹"3"› -- the
359
+ matcher expected the Example to be invalid, but it was valid instead.
360
+
361
+ As indicated in the message above, :attr seems to be changing certain
362
+ values as they are set, and this could have something to do with why
363
+ this test is failing. If you've overridden the writer method for this
364
+ attribute, then you may need to change it to make this test pass, or
365
+ do something else entirely.
366
+ MESSAGE
367
+ }
368
+ }
369
+ ) do
370
+ def validation_matcher_scenario_args
371
+ super.deep_merge(validation_options: { odd: true })
372
+ end
373
+
374
+ def configure_validation_matcher(matcher)
375
+ matcher.odd
376
+ end
377
+ end
378
+
379
+ context 'when the attribute is a virtual attribute in ActiveRecord model' do
380
+ it 'accepts' do
381
+ record = build_record_validating_numericality_of_virtual_attribute(
382
+ odd: true
383
+ )
384
+ expect(record).to validate_numericality.odd
385
+ end
386
+ end
387
+
227
388
  context 'when the column is an integer column' do
228
389
  it 'accepts (and does not raise an error)' do
229
390
  record = build_record_validating_numericality(
@@ -261,12 +422,18 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
261
422
  context 'and not validating with odd' do
262
423
  it 'rejects since it does not disallow even numbers' do
263
424
  record = build_record_validating_numericality
425
+
264
426
  assertion = lambda do
265
427
  expect(record).to validate_numericality.odd
266
428
  end
267
- expect(&assertion).to fail_with_message_including(
268
- 'Expected errors to include "must be odd"'
269
- )
429
+
430
+ message = <<-MESSAGE
431
+ Example did not properly validate that :attr looks like an odd number.
432
+ After setting :attr to ‹"2"›, the matcher expected the Example to be
433
+ invalid, but it was valid instead.
434
+ MESSAGE
435
+
436
+ expect(&assertion).to fail_with_message(message)
270
437
  end
271
438
  end
272
439
  end
@@ -278,6 +445,48 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
278
445
  expect(record).to validate_numericality.even
279
446
  end
280
447
 
448
+ it_supports(
449
+ 'ignoring_interference_by_writer',
450
+ tests: {
451
+ accept_if_qualified_but_changing_value_does_not_interfere: {
452
+ changing_values_with: :next_next_value,
453
+ },
454
+ reject_if_qualified_but_changing_value_interferes: {
455
+ model_name: 'Example',
456
+ attribute_name: :attr,
457
+ changing_values_with: :next_value,
458
+ expected_message: <<-MESSAGE.strip
459
+ Example did not properly validate that :attr looks like an even number.
460
+ After setting :attr to ‹"1"› -- which was read back as ‹"2"› -- the
461
+ matcher expected the Example to be invalid, but it was valid instead.
462
+
463
+ As indicated in the message above, :attr seems to be changing certain
464
+ values as they are set, and this could have something to do with why
465
+ this test is failing. If you've overridden the writer method for this
466
+ attribute, then you may need to change it to make this test pass, or
467
+ do something else entirely.
468
+ MESSAGE
469
+ }
470
+ }
471
+ ) do
472
+ def validation_matcher_scenario_args
473
+ super.deep_merge(validation_options: { even: true })
474
+ end
475
+
476
+ def configure_validation_matcher(matcher)
477
+ matcher.even
478
+ end
479
+ end
480
+
481
+ context 'when the attribute is a virtual attribute in an ActiveRecord model' do
482
+ it 'accepts' do
483
+ record = build_record_validating_numericality_of_virtual_attribute(
484
+ even: true,
485
+ )
486
+ expect(record).to validate_numericality.even
487
+ end
488
+ end
489
+
281
490
  context 'when the column is an integer column' do
282
491
  it 'accepts (and does not raise an error)' do
283
492
  record = build_record_validating_numericality(
@@ -315,10 +524,18 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
315
524
  context 'and not validating with even' do
316
525
  it 'rejects since it does not disallow odd numbers' do
317
526
  record = build_record_validating_numericality
318
- assertion = -> { expect(record).to validate_numericality.even }
319
- expect(&assertion).to fail_with_message_including(
320
- 'Expected errors to include "must be even"'
321
- )
527
+
528
+ assertion = lambda do
529
+ expect(record).to validate_numericality.even
530
+ end
531
+
532
+ message = <<-MESSAGE
533
+ Example did not properly validate that :attr looks like an even number.
534
+ After setting :attr to ‹"1"›, the matcher expected the Example to be
535
+ invalid, but it was valid instead.
536
+ MESSAGE
537
+
538
+ expect(&assertion).to fail_with_message(message)
322
539
  end
323
540
  end
324
541
  end
@@ -332,6 +549,51 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
332
549
  expect(record).to validate_numericality.is_less_than_or_equal_to(18)
333
550
  end
334
551
 
552
+ it_supports(
553
+ 'ignoring_interference_by_writer',
554
+ tests: {
555
+ reject_if_qualified_but_changing_value_interferes: {
556
+ model_name: 'Example',
557
+ attribute_name: :attr,
558
+ changing_values_with: :next_value,
559
+ expected_message: <<-MESSAGE.strip
560
+ Example did not properly validate that :attr looks like a number less
561
+ than or equal to 18.
562
+ After setting :attr to ‹"18"› -- which was read back as ‹"19"› -- the
563
+ matcher expected the Example to be valid, but it was invalid instead,
564
+ producing these validation errors:
565
+
566
+ * attr: ["must be less than or equal to 18"]
567
+
568
+ As indicated in the message above, :attr seems to be changing certain
569
+ values as they are set, and this could have something to do with why
570
+ this test is failing. If you've overridden the writer method for this
571
+ attribute, then you may need to change it to make this test pass, or
572
+ do something else entirely.
573
+ MESSAGE
574
+ }
575
+ }
576
+ ) do
577
+ def validation_matcher_scenario_args
578
+ super.deep_merge(
579
+ validation_options: { less_than_or_equal_to: 18 }
580
+ )
581
+ end
582
+
583
+ def configure_validation_matcher(matcher)
584
+ matcher.is_less_than_or_equal_to(18)
585
+ end
586
+ end
587
+
588
+ context 'when the attribute is a virtual attribute in an ActiveRecord model' do
589
+ it 'accepts' do
590
+ record = build_record_validating_numericality_of_virtual_attribute(
591
+ less_than_or_equal_to: 18,
592
+ )
593
+ expect(record).to validate_numericality.is_less_than_or_equal_to(18)
594
+ end
595
+ end
596
+
335
597
  context 'when the column is an integer column' do
336
598
  it 'accepts (and does not raise an error)' do
337
599
  record = build_record_validating_numericality(
@@ -369,14 +631,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
369
631
  context 'and not validating with less_than_or_equal_to' do
370
632
  it 'rejects since it does not disallow numbers greater than the value' do
371
633
  record = build_record_validating_numericality
634
+
372
635
  assertion = lambda do
373
- expect(record).
374
- to validate_numericality.
375
- is_less_than_or_equal_to(18)
636
+ expect(record).to validate_numericality.is_less_than_or_equal_to(18)
376
637
  end
377
- expect(&assertion).to fail_with_message_including(
378
- 'Expected errors to include "must be less than or equal to 18"'
379
- )
638
+
639
+ message = <<-MESSAGE
640
+ Example did not properly validate that :attr looks like a number less
641
+ than or equal to 18.
642
+ After setting :attr to ‹"19"›, the matcher expected the Example to be
643
+ invalid, but it was valid instead.
644
+ MESSAGE
645
+
646
+ expect(&assertion).to fail_with_message(message)
380
647
  end
381
648
  end
382
649
  end
@@ -390,6 +657,49 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
390
657
  is_less_than(18)
391
658
  end
392
659
 
660
+ it_supports(
661
+ 'ignoring_interference_by_writer',
662
+ tests: {
663
+ reject_if_qualified_but_changing_value_interferes: {
664
+ model_name: 'Example',
665
+ attribute_name: :attr,
666
+ changing_values_with: :next_value,
667
+ expected_message: <<-MESSAGE.strip
668
+ Example did not properly validate that :attr looks like a number less
669
+ than 18.
670
+ After setting :attr to ‹"17"› -- which was read back as ‹"18"› -- the
671
+ matcher expected the Example to be valid, but it was invalid instead,
672
+ producing these validation errors:
673
+
674
+ * attr: ["must be less than 18"]
675
+
676
+ As indicated in the message above, :attr seems to be changing certain
677
+ values as they are set, and this could have something to do with why
678
+ this test is failing. If you've overridden the writer method for this
679
+ attribute, then you may need to change it to make this test pass, or
680
+ do something else entirely.
681
+ MESSAGE
682
+ }
683
+ }
684
+ ) do
685
+ def validation_matcher_scenario_args
686
+ super.deep_merge(validation_options: { less_than: 18 })
687
+ end
688
+
689
+ def configure_validation_matcher(matcher)
690
+ matcher.is_less_than(18)
691
+ end
692
+ end
693
+
694
+ context 'when the attribute is a virtual attribute in an ActiveRecord model' do
695
+ it 'accepts' do
696
+ record = build_record_validating_numericality_of_virtual_attribute(
697
+ less_than: 18,
698
+ )
699
+ expect(record).to validate_numericality.is_less_than(18)
700
+ end
701
+ end
702
+
393
703
  context 'when the column is an integer column' do
394
704
  it 'accepts (and does not raise an error)' do
395
705
  record = build_record_validating_numericality(
@@ -427,14 +737,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
427
737
  context 'and not validating with less_than' do
428
738
  it 'rejects since it does not disallow numbers greater than or equal to the value' do
429
739
  record = build_record_validating_numericality
740
+
430
741
  assertion = lambda do
431
- expect(record).
432
- to validate_numericality.
433
- is_less_than(18)
742
+ expect(record).to validate_numericality.is_less_than(18)
434
743
  end
435
- expect(&assertion).to fail_with_message_including(
436
- 'Expected errors to include "must be less than 18"'
437
- )
744
+
745
+ message = <<-MESSAGE
746
+ Example did not properly validate that :attr looks like a number less
747
+ than 18.
748
+ After setting :attr to ‹"19"›, the matcher expected the Example to be
749
+ invalid, but it was valid instead.
750
+ MESSAGE
751
+
752
+ expect(&assertion).to fail_with_message(message)
438
753
  end
439
754
  end
440
755
  end
@@ -446,6 +761,49 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
446
761
  expect(record).to validate_numericality.is_equal_to(18)
447
762
  end
448
763
 
764
+ it_supports(
765
+ 'ignoring_interference_by_writer',
766
+ tests: {
767
+ reject_if_qualified_but_changing_value_interferes: {
768
+ model_name: 'Example',
769
+ attribute_name: :attr,
770
+ changing_values_with: :next_value,
771
+ expected_message: <<-MESSAGE.strip
772
+ Example did not properly validate that :attr looks like a number equal
773
+ to 18.
774
+ After setting :attr to ‹"18"› -- which was read back as ‹"19"› -- the
775
+ matcher expected the Example to be valid, but it was invalid instead,
776
+ producing these validation errors:
777
+
778
+ * attr: ["must be equal to 18"]
779
+
780
+ As indicated in the message above, :attr seems to be changing certain
781
+ values as they are set, and this could have something to do with why
782
+ this test is failing. If you've overridden the writer method for this
783
+ attribute, then you may need to change it to make this test pass, or
784
+ do something else entirely.
785
+ MESSAGE
786
+ }
787
+ }
788
+ ) do
789
+ def validation_matcher_scenario_args
790
+ super.deep_merge(validation_options: { equal_to: 18 })
791
+ end
792
+
793
+ def configure_validation_matcher(matcher)
794
+ matcher.is_equal_to(18)
795
+ end
796
+ end
797
+
798
+ context 'when the attribute is a virtual attribute in an ActiveRecord model' do
799
+ it 'accepts' do
800
+ record = build_record_validating_numericality_of_virtual_attribute(
801
+ equal_to: 18,
802
+ )
803
+ expect(record).to validate_numericality.is_equal_to(18)
804
+ end
805
+ end
806
+
449
807
  context 'when the column is an integer column' do
450
808
  it 'accepts (and does not raise an error)' do
451
809
  record = build_record_validating_numericality(
@@ -483,12 +841,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
483
841
  context 'and not validating with equal_to' do
484
842
  it 'rejects since it does not disallow numbers that are not the value' do
485
843
  record = build_record_validating_numericality
844
+
486
845
  assertion = lambda do
487
846
  expect(record).to validate_numericality.is_equal_to(18)
488
847
  end
489
- expect(&assertion).to fail_with_message_including(
490
- 'Expected errors to include "must be equal to 18"'
491
- )
848
+
849
+ message = <<-MESSAGE
850
+ Example did not properly validate that :attr looks like a number equal
851
+ to 18.
852
+ After setting :attr to ‹"19"›, the matcher expected the Example to be
853
+ invalid, but it was valid instead.
854
+ MESSAGE
855
+
856
+ expect(&assertion).to fail_with_message(message)
492
857
  end
493
858
  end
494
859
  end
@@ -504,6 +869,49 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
504
869
  is_greater_than_or_equal_to(18)
505
870
  end
506
871
 
872
+ it_supports(
873
+ 'ignoring_interference_by_writer',
874
+ tests: {
875
+ reject_if_qualified_but_changing_value_interferes: {
876
+ model_name: 'Example',
877
+ attribute_name: :attr,
878
+ changing_values_with: :next_value,
879
+ expected_message: <<-MESSAGE.strip
880
+ Example did not properly validate that :attr looks like a number greater
881
+ than or equal to 18.
882
+ After setting :attr to ‹"17"› -- which was read back as ‹"18"› -- the
883
+ matcher expected the Example to be invalid, but it was valid instead.
884
+
885
+ As indicated in the message above, :attr seems to be changing certain
886
+ values as they are set, and this could have something to do with why
887
+ this test is failing. If you've overridden the writer method for this
888
+ attribute, then you may need to change it to make this test pass, or
889
+ do something else entirely.
890
+ MESSAGE
891
+ }
892
+ }
893
+ ) do
894
+ def validation_matcher_scenario_args
895
+ super.deep_merge(
896
+ validation_options: { greater_than_or_equal_to: 18 }
897
+ )
898
+ end
899
+
900
+ def configure_validation_matcher(matcher)
901
+ matcher.is_greater_than_or_equal_to(18)
902
+ end
903
+ end
904
+
905
+ context 'when the attribute is a virtual attribute in an ActiveRecord model' do
906
+ it 'accepts' do
907
+ record = build_record_validating_numericality_of_virtual_attribute(
908
+ greater_than_or_equal_to: 18,
909
+ )
910
+ expect(record).to validate_numericality.
911
+ is_greater_than_or_equal_to(18)
912
+ end
913
+ end
914
+
507
915
  context 'when the column is an integer column' do
508
916
  it 'accepts (and does not raise an error)' do
509
917
  record = build_record_validating_numericality(
@@ -547,14 +955,20 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
547
955
  context 'not validating with greater_than_or_equal_to' do
548
956
  it 'rejects since it does not disallow numbers that are less than the value' do
549
957
  record = build_record_validating_numericality
958
+
550
959
  assertion = lambda do
551
- expect(record).
552
- to validate_numericality.
960
+ expect(record).to validate_numericality.
553
961
  is_greater_than_or_equal_to(18)
554
962
  end
555
- expect(&assertion).to fail_with_message_including(
556
- 'Expected errors to include "must be greater than or equal to 18"'
557
- )
963
+
964
+ message = <<-MESSAGE
965
+ Example did not properly validate that :attr looks like a number greater
966
+ than or equal to 18.
967
+ After setting :attr to ‹"17"›, the matcher expected the Example to be
968
+ invalid, but it was valid instead.
969
+ MESSAGE
970
+
971
+ expect(&assertion).to fail_with_message(message)
558
972
  end
559
973
  end
560
974
  end
@@ -568,6 +982,46 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
568
982
  is_greater_than(18)
569
983
  end
570
984
 
985
+ it_supports(
986
+ 'ignoring_interference_by_writer',
987
+ tests: {
988
+ reject_if_qualified_but_changing_value_interferes: {
989
+ model_name: 'Example',
990
+ attribute_name: :attr,
991
+ changing_values_with: :next_value,
992
+ expected_message: <<-MESSAGE.strip
993
+ Example did not properly validate that :attr looks like a number greater
994
+ than 18.
995
+ After setting :attr to ‹"18"› -- which was read back as ‹"19"› -- the
996
+ matcher expected the Example to be invalid, but it was valid instead.
997
+
998
+ As indicated in the message above, :attr seems to be changing certain
999
+ values as they are set, and this could have something to do with why
1000
+ this test is failing. If you've overridden the writer method for this
1001
+ attribute, then you may need to change it to make this test pass, or
1002
+ do something else entirely.
1003
+ MESSAGE
1004
+ }
1005
+ }
1006
+ ) do
1007
+ def validation_matcher_scenario_args
1008
+ super.deep_merge(validation_options: { greater_than: 18 })
1009
+ end
1010
+
1011
+ def configure_validation_matcher(matcher)
1012
+ matcher.is_greater_than(18)
1013
+ end
1014
+ end
1015
+
1016
+ context 'when the attribute is a virtual attribute in an ActiveRecord model' do
1017
+ it 'accepts' do
1018
+ record = build_record_validating_numericality_of_virtual_attribute(
1019
+ greater_than: 18,
1020
+ )
1021
+ expect(record).to validate_numericality.is_greater_than(18)
1022
+ end
1023
+ end
1024
+
571
1025
  context 'when the column is an integer column' do
572
1026
  it 'accepts (and does not raise an error)' do
573
1027
  record = build_record_validating_numericality(
@@ -611,14 +1065,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
611
1065
  context 'and not validating with greater_than' do
612
1066
  it 'rejects since it does not disallow numbers that are less than or equal to the value' do
613
1067
  record = build_record_validating_numericality
1068
+
614
1069
  assertion = lambda do
615
- expect(record).
616
- to validate_numericality.
617
- is_greater_than(18)
1070
+ expect(record).to validate_numericality.is_greater_than(18)
618
1071
  end
619
- expect(&assertion).to fail_with_message_including(
620
- 'Expected errors to include "must be greater than 18"'
621
- )
1072
+
1073
+ message = <<-MESSAGE
1074
+ Example did not properly validate that :attr looks like a number greater
1075
+ than 18.
1076
+ After setting :attr to ‹"18"›, the matcher expected the Example to be
1077
+ invalid, but it was valid instead.
1078
+ MESSAGE
1079
+
1080
+ expect(&assertion).to fail_with_message(message)
622
1081
  end
623
1082
  end
624
1083
  end
@@ -632,9 +1091,25 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
632
1091
  end
633
1092
 
634
1093
  context 'and validating with a different message' do
635
- it 'rejects' do
1094
+ it 'rejects with the correct failure message' do
636
1095
  record = build_record_validating_numericality(message: 'custom')
637
- expect(record).not_to validate_numericality.with_message(/wrong/)
1096
+
1097
+ assertion = lambda do
1098
+ expect(record).to validate_numericality.with_message(/wrong/)
1099
+ end
1100
+
1101
+ message = <<-MESSAGE
1102
+ Example did not properly validate that :attr looks like a number,
1103
+ producing a custom validation error on failure.
1104
+ After setting :attr to ‹"abcd"›, the matcher expected the Example to
1105
+ be invalid and to produce a validation error matching ‹/wrong/› on
1106
+ :attr. The record was indeed invalid, but it produced these validation
1107
+ errors instead:
1108
+
1109
+ * attr: ["custom"]
1110
+ MESSAGE
1111
+
1112
+ expect(&assertion).to fail_with_message(message)
638
1113
  end
639
1114
  end
640
1115
 
@@ -644,6 +1119,25 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
644
1119
  expect(record).to validate_numericality.with_message(nil)
645
1120
  end
646
1121
  end
1122
+
1123
+ context 'and the validation is missing from the model' do
1124
+ it 'rejects with the correct failure message' do
1125
+ model = define_model_validating_nothing
1126
+
1127
+ assertion = lambda do
1128
+ expect(model.new).to validate_numericality.with_message(/wrong/)
1129
+ end
1130
+
1131
+ message = <<-MESSAGE
1132
+ Example did not properly validate that :attr looks like a number,
1133
+ producing a custom validation error on failure.
1134
+ After setting :attr to ‹"abcd"›, the matcher expected the Example to
1135
+ be invalid, but it was valid instead.
1136
+ MESSAGE
1137
+
1138
+ expect(&assertion).to fail_with_message(message)
1139
+ end
1140
+ end
647
1141
  end
648
1142
 
649
1143
  context 'qualified with strict' do
@@ -657,12 +1151,20 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
657
1151
  context 'and not validating strictly' do
658
1152
  it 'rejects since ActiveModel::StrictValidationFailed is never raised' do
659
1153
  record = build_record_validating_numericality(attribute_name: :attr)
1154
+
660
1155
  assertion = lambda do
661
1156
  expect(record).to validate_numericality_of(:attr).strict
662
1157
  end
663
- expect(&assertion).to fail_with_message_including(
664
- 'Expected exception to include "Attr is not a number"'
665
- )
1158
+
1159
+ message = <<-MESSAGE
1160
+ Example did not properly validate that :attr looks like a number,
1161
+ raising a validation exception on failure.
1162
+ After setting :attr to ‹"abcd"›, the matcher expected the Example to
1163
+ be invalid and to raise a validation exception, but the record
1164
+ produced validation errors instead.
1165
+ MESSAGE
1166
+
1167
+ expect(&assertion).to fail_with_message(message)
666
1168
  end
667
1169
  end
668
1170
  end
@@ -684,12 +1186,18 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
684
1186
  context 'not qualified with on but validating with on' do
685
1187
  it 'rejects since the validation never runs' do
686
1188
  record = build_record_validating_numericality(on: :customizable)
1189
+
687
1190
  assertion = lambda do
688
1191
  expect(record).to validate_numericality
689
1192
  end
690
- expect(&assertion).to fail_with_message_including(
691
- 'Expected errors to include "is not a number"'
692
- )
1193
+
1194
+ message = <<-MESSAGE
1195
+ Example did not properly validate that :attr looks like a number.
1196
+ After setting :attr to ‹"abcd"›, the matcher expected the Example to
1197
+ be invalid, but it was valid instead.
1198
+ MESSAGE
1199
+
1200
+ expect(&assertion).to fail_with_message(message)
693
1201
  end
694
1202
  end
695
1203
 
@@ -712,33 +1220,51 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
712
1220
  even: true,
713
1221
  greater_than: 18
714
1222
  )
1223
+
715
1224
  assertion = lambda do
716
1225
  expect(record).
717
1226
  to validate_numericality.
718
1227
  only_integer.
719
1228
  is_greater_than(18)
720
1229
  end
721
- message = <<-MESSAGE.strip_heredoc
722
- Expected errors to include "must be an integer" when attr is set to "0.1",
723
- got errors:
724
- * "must be greater than 18" (attribute: attr, value: "0.1")
1230
+
1231
+ message = <<-MESSAGE
1232
+ Example did not properly validate that :attr looks like an integer
1233
+ greater than 18.
1234
+ In checking that Example disallows :attr from being a decimal number,
1235
+ after setting :attr to ‹"0.1"›, the matcher expected the Example to be
1236
+ invalid and to produce the validation error "must be an integer" on
1237
+ :attr. The record was indeed invalid, but it produced these validation
1238
+ errors instead:
1239
+
1240
+ * attr: ["must be greater than 18"]
725
1241
  MESSAGE
1242
+
726
1243
  expect(&assertion).to fail_with_message(message)
727
1244
  end
728
1245
 
729
1246
  specify 'such as not validating only_integer but testing that only_integer is validated' do
730
1247
  record = build_record_validating_numericality(greater_than: 18)
1248
+
731
1249
  assertion = lambda do
732
1250
  expect(record).
733
1251
  to validate_numericality.
734
1252
  only_integer.
735
1253
  is_greater_than(18)
736
1254
  end
1255
+
737
1256
  message = <<-MESSAGE.strip_heredoc
738
- Expected errors to include "must be an integer" when attr is set to "0.1",
739
- got errors:
740
- * "must be greater than 18" (attribute: attr, value: "0.1")
1257
+ Example did not properly validate that :attr looks like an integer
1258
+ greater than 18.
1259
+ In checking that Example disallows :attr from being a decimal number,
1260
+ after setting :attr to ‹"0.1"›, the matcher expected the Example to be
1261
+ invalid and to produce the validation error "must be an integer" on
1262
+ :attr. The record was indeed invalid, but it produced these validation
1263
+ errors instead:
1264
+
1265
+ * attr: ["must be greater than 18"]
741
1266
  MESSAGE
1267
+
742
1268
  expect(&assertion).to fail_with_message(message)
743
1269
  end
744
1270
 
@@ -747,16 +1273,22 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
747
1273
  even: true,
748
1274
  greater_than_or_equal_to: 18
749
1275
  )
1276
+
750
1277
  assertion = lambda do
751
1278
  expect(record).
752
1279
  to validate_numericality.
753
1280
  even.
754
1281
  is_greater_than(18)
755
1282
  end
756
- message = <<-MESSAGE.strip_heredoc
757
- Expected errors to include "must be greater than 18" when attr is set to "18",
758
- got no errors
1283
+
1284
+ message = <<-MESSAGE
1285
+ Example did not properly validate that :attr looks like an even number
1286
+ greater than 18.
1287
+ In checking that Example disallows :attr from being a number that is
1288
+ not greater than 18, after setting :attr to ‹"18"›, the matcher
1289
+ expected the Example to be invalid, but it was valid instead.
759
1290
  MESSAGE
1291
+
760
1292
  expect(&assertion).to fail_with_message(message)
761
1293
  end
762
1294
 
@@ -765,17 +1297,26 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
765
1297
  odd: true,
766
1298
  greater_than: 18
767
1299
  )
1300
+
768
1301
  assertion = lambda do
769
1302
  expect(record).
770
1303
  to validate_numericality.
771
1304
  even.
772
1305
  is_greater_than(18)
773
1306
  end
774
- message = <<-MESSAGE.strip_heredoc
775
- Expected errors to include "must be even" when attr is set to "1",
776
- got errors:
777
- * "must be greater than 18" (attribute: attr, value: "1")
1307
+
1308
+ message = <<-MESSAGE
1309
+ Example did not properly validate that :attr looks like an even number
1310
+ greater than 18.
1311
+ In checking that Example disallows :attr from being an odd number,
1312
+ after setting :attr to ‹"1"›, the matcher expected the Example to be
1313
+ invalid and to produce the validation error "must be even" on :attr.
1314
+ The record was indeed invalid, but it produced these validation errors
1315
+ instead:
1316
+
1317
+ * attr: ["must be greater than 18"]
778
1318
  MESSAGE
1319
+
779
1320
  expect(&assertion).to fail_with_message(message)
780
1321
  end
781
1322
 
@@ -784,16 +1325,22 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
784
1325
  odd: true,
785
1326
  greater_than_or_equal_to: 99
786
1327
  )
1328
+
787
1329
  assertion = lambda do
788
1330
  expect(record).
789
1331
  to validate_numericality.
790
1332
  odd.
791
1333
  is_less_than_or_equal_to(99)
792
1334
  end
793
- message = <<-MESSAGE.strip_heredoc
794
- Expected errors to include "must be less than or equal to 99" when attr is set to "101",
795
- got no errors
1335
+
1336
+ message = <<-MESSAGE
1337
+ Example did not properly validate that :attr looks like an odd number
1338
+ less than or equal to 99.
1339
+ In checking that Example disallows :attr from being a number that is
1340
+ not less than or equal to 99, after setting :attr to ‹"101"›, the
1341
+ matcher expected the Example to be invalid, but it was valid instead.
796
1342
  MESSAGE
1343
+
797
1344
  expect(&assertion).to fail_with_message(message)
798
1345
  end
799
1346
 
@@ -803,6 +1350,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
803
1350
  greater_than_or_equal_to: 18,
804
1351
  less_than: 99
805
1352
  )
1353
+
806
1354
  assertion = lambda do
807
1355
  expect(record).
808
1356
  to validate_numericality.
@@ -810,10 +1358,15 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
810
1358
  is_greater_than(18).
811
1359
  is_less_than(99)
812
1360
  end
813
- message = <<-MESSAGE.strip_heredoc
814
- Expected errors to include "must be greater than 18" when attr is set to "18",
815
- got no errors
1361
+
1362
+ message = <<-MESSAGE
1363
+ Example did not properly validate that :attr looks like an integer
1364
+ greater than 18 and less than 99.
1365
+ In checking that Example disallows :attr from being a number that is
1366
+ not greater than 18, after setting :attr to ‹"18"›, the matcher
1367
+ expected the Example to be invalid, but it was valid instead.
816
1368
  MESSAGE
1369
+
817
1370
  expect(&assertion).to fail_with_message(message)
818
1371
  end
819
1372
  end
@@ -824,17 +1377,27 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
824
1377
  only_integer: true,
825
1378
  greater_than: 19
826
1379
  )
1380
+
827
1381
  assertion = lambda do
828
1382
  expect(record).
829
1383
  to validate_numericality.
830
1384
  only_integer.
831
1385
  is_greater_than(18)
832
1386
  end
833
- message = <<-MESSAGE.strip_heredoc
834
- Expected errors to include "must be greater than 18" when attr is set to "18",
835
- got errors:
836
- * "must be greater than 19" (attribute: attr, value: "19")
1387
+
1388
+ # why is value "19" here?
1389
+ message = <<-MESSAGE
1390
+ Example did not properly validate that :attr looks like an integer
1391
+ greater than 18.
1392
+ In checking that Example disallows :attr from being a number that is
1393
+ not greater than 18, after setting :attr to ‹"18"›, the matcher
1394
+ expected the Example to be invalid and to produce the validation error
1395
+ "must be greater than 18" on :attr. The record was indeed invalid, but
1396
+ it produced these validation errors instead:
1397
+
1398
+ * attr: ["must be greater than 19"]
837
1399
  MESSAGE
1400
+
838
1401
  expect(&assertion).to fail_with_message(message)
839
1402
  end
840
1403
 
@@ -843,16 +1406,22 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
843
1406
  only_integer: true,
844
1407
  greater_than: 17
845
1408
  )
1409
+
846
1410
  assertion = lambda do
847
1411
  expect(record).
848
1412
  to validate_numericality.
849
1413
  only_integer.
850
1414
  is_greater_than(18)
851
1415
  end
852
- message = <<-MESSAGE.strip_heredoc
853
- Expected errors to include "must be greater than 18" when attr is set to "18",
854
- got no errors
1416
+
1417
+ message = <<-MESSAGE
1418
+ Example did not properly validate that :attr looks like an integer
1419
+ greater than 18.
1420
+ In checking that Example disallows :attr from being a number that is
1421
+ not greater than 18, after setting :attr to ‹"18"›, the matcher
1422
+ expected the Example to be invalid, but it was valid instead.
855
1423
  MESSAGE
1424
+
856
1425
  expect(&assertion).to fail_with_message(message)
857
1426
  end
858
1427
 
@@ -861,17 +1430,27 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
861
1430
  even: true,
862
1431
  greater_than: 20
863
1432
  )
1433
+
864
1434
  assertion = lambda do
865
1435
  expect(record).
866
1436
  to validate_numericality.
867
1437
  even.
868
1438
  is_greater_than(18)
869
1439
  end
870
- message = <<-MESSAGE.strip_heredoc
871
- Expected errors to include "must be greater than 18" when attr is set to "18",
872
- got errors:
873
- * "must be greater than 20" (attribute: attr, value: "20")
1440
+
1441
+ # why is value "20" here?
1442
+ message = <<-MESSAGE
1443
+ Example did not properly validate that :attr looks like an even number
1444
+ greater than 18.
1445
+ In checking that Example disallows :attr from being a number that is
1446
+ not greater than 18, after setting :attr to ‹"18"›, the matcher
1447
+ expected the Example to be invalid and to produce the validation error
1448
+ "must be greater than 18" on :attr. The record was indeed invalid, but
1449
+ it produced these validation errors instead:
1450
+
1451
+ * attr: ["must be greater than 20"]
874
1452
  MESSAGE
1453
+
875
1454
  expect(&assertion).to fail_with_message(message)
876
1455
  end
877
1456
 
@@ -880,16 +1459,22 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
880
1459
  even: true,
881
1460
  greater_than: 16
882
1461
  )
1462
+
883
1463
  assertion = lambda do
884
1464
  expect(record).
885
1465
  to validate_numericality.
886
1466
  even.
887
1467
  is_greater_than(18)
888
1468
  end
889
- message = <<-MESSAGE.strip_heredoc
890
- Expected errors to include "must be greater than 18" when attr is set to "18",
891
- got no errors
1469
+
1470
+ message = <<-MESSAGE
1471
+ Example did not properly validate that :attr looks like an even number
1472
+ greater than 18.
1473
+ In checking that Example disallows :attr from being a number that is
1474
+ not greater than 18, after setting :attr to ‹"18"›, the matcher
1475
+ expected the Example to be invalid, but it was valid instead.
892
1476
  MESSAGE
1477
+
893
1478
  expect(&assertion).to fail_with_message(message)
894
1479
  end
895
1480
 
@@ -898,16 +1483,22 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
898
1483
  odd: true,
899
1484
  less_than_or_equal_to: 101
900
1485
  )
1486
+
901
1487
  assertion = lambda do
902
1488
  expect(record).
903
1489
  to validate_numericality.
904
1490
  odd.
905
1491
  is_less_than_or_equal_to(99)
906
1492
  end
907
- message = <<-MESSAGE.strip_heredoc
908
- Expected errors to include "must be less than or equal to 99" when attr is set to "101",
909
- got no errors
1493
+
1494
+ message = <<-MESSAGE
1495
+ Example did not properly validate that :attr looks like an odd number
1496
+ less than or equal to 99.
1497
+ In checking that Example disallows :attr from being a number that is
1498
+ not less than or equal to 99, after setting :attr to ‹"101"›, the
1499
+ matcher expected the Example to be invalid, but it was valid instead.
910
1500
  MESSAGE
1501
+
911
1502
  expect(&assertion).to fail_with_message(message)
912
1503
  end
913
1504
 
@@ -916,17 +1507,27 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
916
1507
  odd: true,
917
1508
  less_than_or_equal_to: 97
918
1509
  )
1510
+
919
1511
  assertion = lambda do
920
1512
  expect(record).
921
1513
  to validate_numericality.
922
1514
  odd.
923
1515
  is_less_than_or_equal_to(99)
924
1516
  end
925
- message = <<-MESSAGE.strip_heredoc
926
- Expected errors to include "must be less than or equal to 99" when attr is set to "101",
927
- got errors:
928
- * "must be less than or equal to 97" (attribute: attr, value: "101")
1517
+
1518
+ message = <<-MESSAGE
1519
+ Example did not properly validate that :attr looks like an odd number
1520
+ less than or equal to 99.
1521
+ In checking that Example disallows :attr from being a number that is
1522
+ not less than or equal to 99, after setting :attr to ‹"101"›, the
1523
+ matcher expected the Example to be invalid and to produce the
1524
+ validation error "must be less than or equal to 99" on :attr. The
1525
+ record was indeed invalid, but it produced these validation errors
1526
+ instead:
1527
+
1528
+ * attr: ["must be less than or equal to 97"]
929
1529
  MESSAGE
1530
+
930
1531
  expect(&assertion).to fail_with_message(message)
931
1532
  end
932
1533
 
@@ -936,6 +1537,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
936
1537
  greater_than: 19,
937
1538
  less_than: 99
938
1539
  )
1540
+
939
1541
  assertion = lambda do
940
1542
  expect(record).
941
1543
  to validate_numericality.
@@ -943,11 +1545,20 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
943
1545
  is_greater_than(18).
944
1546
  is_less_than(99)
945
1547
  end
946
- message = <<-MESSAGE.strip_heredoc
947
- Expected errors to include "must be greater than 18" when attr is set to "18",
948
- got errors:
949
- * "must be greater than 19" (attribute: attr, value: "19")
1548
+
1549
+ # why is value "19" here?
1550
+ message = <<-MESSAGE
1551
+ Example did not properly validate that :attr looks like an integer
1552
+ greater than 18 and less than 99.
1553
+ In checking that Example disallows :attr from being a number that is
1554
+ not greater than 18, after setting :attr to ‹"18"›, the matcher
1555
+ expected the Example to be invalid and to produce the validation error
1556
+ "must be greater than 18" on :attr. The record was indeed invalid, but
1557
+ it produced these validation errors instead:
1558
+
1559
+ * attr: ["must be greater than 19"]
950
1560
  MESSAGE
1561
+
951
1562
  expect(&assertion).to fail_with_message(message)
952
1563
  end
953
1564
 
@@ -957,6 +1568,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
957
1568
  greater_than: 18,
958
1569
  less_than: 100
959
1570
  )
1571
+
960
1572
  assertion = lambda do
961
1573
  expect(record).
962
1574
  to validate_numericality.
@@ -964,11 +1576,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
964
1576
  is_greater_than(18).
965
1577
  is_less_than(99)
966
1578
  end
967
- message = <<-MESSAGE.strip_heredoc
968
- Expected errors to include "must be less than 99" when attr is set to "100",
969
- got errors:
970
- * "must be less than 100" (attribute: attr, value: "100")
1579
+
1580
+ message = <<-MESSAGE
1581
+ Example did not properly validate that :attr looks like an integer
1582
+ greater than 18 and less than 99.
1583
+ In checking that Example disallows :attr from being a number that is
1584
+ not less than 99, after setting :attr to ‹"100"›, the matcher expected
1585
+ the Example to be invalid and to produce the validation error "must be
1586
+ less than 99" on :attr. The record was indeed invalid, but it produced
1587
+ these validation errors instead:
1588
+
1589
+ * attr: ["must be less than 100"]
971
1590
  MESSAGE
1591
+
972
1592
  expect(&assertion).to fail_with_message(message)
973
1593
  end
974
1594
  end
@@ -1027,20 +1647,53 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1027
1647
 
1028
1648
  expect(model.new).to validate_numericality_of(:attr)
1029
1649
  end
1650
+
1651
+ it_supports(
1652
+ 'ignoring_interference_by_writer',
1653
+ tests: {
1654
+ accept_if_qualified_but_changing_value_does_not_interfere: {
1655
+ changing_values_with: :next_value,
1656
+ },
1657
+ reject_if_qualified_but_changing_value_interferes: {
1658
+ model_name: 'Example',
1659
+ attribute_name: :attr,
1660
+ changing_values_with: :numeric_value,
1661
+ expected_message: <<-MESSAGE.strip
1662
+ Example did not properly validate that :attr looks like a number.
1663
+ After setting :attr to ‹"abcd"› -- which was read back as ‹"1"› -- the
1664
+ matcher expected the Example to be invalid, but it was valid instead.
1665
+
1666
+ As indicated in the message above, :attr seems to be changing certain
1667
+ values as they are set, and this could have something to do with why
1668
+ this test is failing. If you've overridden the writer method for this
1669
+ attribute, then you may need to change it to make this test pass, or
1670
+ do something else entirely.
1671
+ MESSAGE
1672
+ }
1673
+ }
1674
+ )
1675
+
1676
+ def validation_matcher_scenario_args
1677
+ super.deep_merge(model_creator: :active_model)
1678
+ end
1030
1679
  end
1031
1680
 
1032
1681
  describe '#description' do
1033
1682
  context 'qualified with nothing' do
1034
1683
  it 'describes that it allows numbers' do
1035
1684
  matcher = validate_numericality_of(:attr)
1036
- expect(matcher.description).to eq 'only allow numbers for attr'
1685
+ expect(matcher.description).to eq(
1686
+ 'validate that :attr looks like a number'
1687
+ )
1037
1688
  end
1038
1689
  end
1039
1690
 
1040
1691
  context 'qualified with only_integer' do
1041
1692
  it 'describes that it allows integers' do
1042
1693
  matcher = validate_numericality_of(:attr).only_integer
1043
- expect(matcher.description).to eq 'only allow integers for attr'
1694
+ expect(matcher.description).to eq(
1695
+ 'validate that :attr looks like an integer'
1696
+ )
1044
1697
  end
1045
1698
  end
1046
1699
 
@@ -1048,8 +1701,9 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1048
1701
  context "qualified with #{qualifier[:name]}" do
1049
1702
  it "describes that it allows #{qualifier[:name]} numbers" do
1050
1703
  matcher = validate_numericality_of(:attr).__send__(qualifier[:name])
1051
- expect(matcher.description).
1052
- to eq "only allow #{qualifier[:name]} numbers for attr"
1704
+ expect(matcher.description).to eq(
1705
+ "validate that :attr looks like an #{qualifier[:name]} number"
1706
+ )
1053
1707
  end
1054
1708
  end
1055
1709
  end
@@ -1063,7 +1717,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1063
1717
  __send__(qualifier[:name], 18)
1064
1718
 
1065
1719
  expect(matcher.description).to eq(
1066
- "only allow numbers for attr which are #{comparison_phrase} 18"
1720
+ "validate that :attr looks like a number #{comparison_phrase} 18"
1067
1721
  )
1068
1722
  end
1069
1723
  end
@@ -1076,7 +1730,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1076
1730
  is_greater_than_or_equal_to(18)
1077
1731
 
1078
1732
  expect(matcher.description).to eq(
1079
- 'only allow odd numbers for attr which are greater than or equal to 18'
1733
+ 'validate that :attr looks like an odd number greater than or equal to 18'
1080
1734
  )
1081
1735
  end
1082
1736
  end
@@ -1089,7 +1743,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1089
1743
  is_less_than_or_equal_to(100)
1090
1744
 
1091
1745
  expect(matcher.description).to eq(
1092
- 'only allow integers for attr which are greater than 18 and less than or equal to 100'
1746
+ 'validate that :attr looks like an integer greater than 18 and less than or equal to 100'
1093
1747
  )
1094
1748
  end
1095
1749
  end
@@ -1098,7 +1752,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1098
1752
  it 'describes that it relies upon a strict validation' do
1099
1753
  matcher = validate_numericality_of(:attr).strict
1100
1754
  expect(matcher.description).to eq(
1101
- 'only allow numbers for attr, strictly'
1755
+ 'validate that :attr looks like a number, raising a validation exception on failure'
1102
1756
  )
1103
1757
  end
1104
1758
 
@@ -1106,7 +1760,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1106
1760
  it 'places the comparison description after "strictly"' do
1107
1761
  matcher = validate_numericality_of(:attr).is_less_than(18).strict
1108
1762
  expect(matcher.description).to eq(
1109
- 'only allow numbers for attr, strictly, which are less than 18'
1763
+ 'validate that :attr looks like a number less than 18, raising a validation exception on failure'
1110
1764
  )
1111
1765
  end
1112
1766
  end
@@ -1141,6 +1795,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1141
1795
  end
1142
1796
  end
1143
1797
 
1798
+ def define_model_validating_numericality_of_virtual_attribute(options = {})
1799
+ attribute_name = options.delete(:attribute_name) { self.attribute_name }
1800
+
1801
+ define_model 'Example' do |model|
1802
+ model.send(:attr_accessor, attribute_name)
1803
+ model.validates_numericality_of(attribute_name, options)
1804
+ end
1805
+ end
1806
+
1807
+ def build_record_validating_numericality_of_virtual_attribute(options = {})
1808
+ define_model_validating_numericality_of_virtual_attribute(options).new
1809
+ end
1810
+
1144
1811
  def build_record_validating_numericality(options = {})
1145
1812
  define_model_validating_numericality(options).new
1146
1813
  end
@@ -1160,4 +1827,11 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
1160
1827
  def attribute_name
1161
1828
  :attr
1162
1829
  end
1830
+
1831
+ def validation_matcher_scenario_args
1832
+ super.deep_merge(
1833
+ matcher_name: :validate_numericality_of,
1834
+ model_creator: :active_record
1835
+ )
1836
+ end
1163
1837
  end