thoughtbot--shoulda-matchers/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_match...

265 lines
8.8 KiB
Ruby
Raw Normal View History

require 'unit_spec_helper'
2011-10-16 17:49:58 +00:00
describe Shoulda::Matchers::ActiveModel::ValidateExclusionOfMatcher, type: :model do
2012-12-20 05:04:27 +00:00
context 'an attribute which must be excluded from a range' do
it 'accepts ensuring the correct range' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: 2..5)).
to validate_exclusion_of(:attr).in_range(2..5)
2011-10-16 17:49:58 +00:00
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
it 'rejects if the given range spills past the top of the range in the validation' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: 2..5)).
not_to validate_exclusion_of(:attr).in_range(2..6)
2011-10-16 17:49:58 +00:00
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
it 'rejects if the given range falls short of the top of the range in the validation' do
expect(validating_exclusion(in: 2..5)).
not_to validate_exclusion_of(:attr).in_range(2..4)
end
it 'rejects if the given range spills past the bottom of the range in the validation' do
expect(validating_exclusion(in: 2..5)).
not_to validate_exclusion_of(:attr).in_range(1..5)
end
it 'rejects if the given range falls short of the bottom of the range in the validation' do
expect(validating_exclusion(in: 2..5)).
not_to validate_exclusion_of(:attr).in_range(3..5)
end
2012-12-20 05:04:27 +00:00
it 'does not override the default message with a blank' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: 2..5)).
to validate_exclusion_of(:attr).in_range(2..5).with_message(nil)
2011-10-16 17:49:58 +00:00
end
it_supports(
'ignoring_interference_by_writer',
tests: {
reject_if_qualified_but_changing_value_interferes: {
model_name: 'Example',
attribute_name: :attr,
changing_values_with: :next_value,
expected_message: <<-MESSAGE.strip
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
Expected Example to validate that :attr lies outside the range 2 to
5, but this could not be proved.
After setting :attr to 1 -- which was read back as 2 -- the
matcher expected the Example to be valid, but it was invalid instead,
producing these validation errors:
* attr: ["is reserved"]
As indicated in the message above, :attr seems to be changing certain
values as they are set, and this could have something to do with why
this test is failing. If you've overridden the writer method for this
attribute, then you may need to change it to make this test pass, or
do something else entirely.
MESSAGE
},
},
model_creator: :active_model
) do
def validation_matcher_scenario_args
super.deep_merge(validation_options: { in: 2..5 })
end
def configure_validation_matcher(matcher)
matcher.in_range(2..5)
end
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
it 'fails when used in the negative' do
assertion = lambda do
expect(validating_exclusion(in: 2..5)).
not_to validate_exclusion_of(:attr).in_range(2..5)
end
message = <<-MESSAGE
Expected Example not to validate that :attr lies outside the range 2
to 5, but this could not be proved.
After setting :attr to 6, the matcher expected the Example to be
invalid, but it was valid instead.
MESSAGE
expect(&assertion).to fail_with_message(message)
end
2011-10-16 17:49:58 +00:00
end
context 'an attribute which must be excluded from a range with excluded end' do
it 'accepts ensuring the correct range' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: 2...5)).
to validate_exclusion_of(:attr).in_range(2...5)
end
it 'rejects ensuring excluded value' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: 2...5)).
not_to validate_exclusion_of(:attr).in_range(2...4)
end
end
2012-12-20 05:04:27 +00:00
context 'an attribute with a custom validation message' do
it 'accepts ensuring the correct range' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: 2..4, message: 'not good')).
to validate_exclusion_of(:attr).in_range(2..4).with_message(/not good/)
2011-10-16 17:49:58 +00:00
end
it 'accepts ensuring the correct range with an interpolated variable in the message' do
matcher = validating_exclusion(in: 2..4, message: '%{value} is not good')
expect(matcher).
to validate_exclusion_of(:attr).
in_range(2..4).
with_message(/^[234] is not good$/)
end
2011-10-16 17:49:58 +00:00
end
2012-12-20 05:04:27 +00:00
context 'an attribute with custom range validations' do
it 'accepts ensuring the correct range and messages' do
model = custom_validation do
if attr >= 2 && attr <= 5
errors.add(:attr, 'should be out of this range')
2011-10-16 17:49:58 +00:00
end
end
expect(model).to validate_exclusion_of(:attr).in_range(2..5).
2012-12-20 05:04:27 +00:00
with_message(/should be out of this range/)
model = custom_validation do
if attr >= 2 && attr <= 4
errors.add(:attr, 'should be out of this range')
end
end
expect(model).to validate_exclusion_of(:attr).in_range(2...5).
with_message(/should be out of this range/)
2011-10-16 17:49:58 +00:00
end
it 'has correct description' do
matcher = validate_exclusion_of(:attr).in_range(1..10)
expect(matcher.description).to eq(
'validate that :attr lies outside the range 1 to 10'
)
end
2011-10-16 17:49:58 +00:00
end
2012-12-20 05:04:27 +00:00
context 'an attribute which must be excluded from an array' do
it 'accepts with correct array' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: %w(one two))).
to validate_exclusion_of(:attr).in_array(%w(one two))
2012-08-19 09:07:05 +00:00
end
2012-12-20 05:04:27 +00:00
it 'rejects when only part of array matches' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: %w(one two))).
not_to validate_exclusion_of(:attr).in_array(%w(one wrong_value))
2012-08-19 09:07:05 +00:00
end
2012-12-20 05:04:27 +00:00
it 'rejects when array does not match at all' do
2014-01-17 22:01:43 +00:00
expect(validating_exclusion(in: %w(one two))).
not_to validate_exclusion_of(:attr).in_array(%w(cat dog))
2012-08-19 09:07:05 +00:00
end
context 'when there is one value' do
it 'has correct description' do
expect(validate_exclusion_of(:attr).in_array([true]).description).
to eq 'validate that :attr is not true'
end
end
context 'when there are two values' do
it 'has correct description' do
matcher = validate_exclusion_of(:attr).in_array([true, 'dog'])
expect(matcher.description).to eq(
'validate that :attr is neither true nor "dog"'
)
end
end
context 'when there are three or more values' do
it 'has correct description' do
matcher = validate_exclusion_of(:attr).in_array([true, 'dog', 'cat'])
expect(matcher.description).to eq(
'validate that :attr is neither true, "dog", nor "cat"'
)
end
end
it_supports(
'ignoring_interference_by_writer',
tests: {
reject_if_qualified_but_changing_value_interferes: {
model_name: 'Example',
attribute_name: :attr,
changing_values_with: :next_value,
expected_message: <<-MESSAGE.strip
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
Expected Example to validate that :attr is neither "one" nor "two",
but this could not be proved.
After setting :attr to "one" -- which was read back as "onf" --
the matcher expected the Example to be invalid, but it was valid
instead.
As indicated in the message above, :attr seems to be changing certain
values as they are set, and this could have something to do with why
this test is failing. If you've overridden the writer method for this
attribute, then you may need to change it to make this test pass, or
do something else entirely.
MESSAGE
},
},
model_creator: :active_model
) do
def validation_matcher_scenario_args
super.deep_merge(validation_options: { in: ['one', 'two'] })
end
def configure_validation_matcher(matcher)
matcher.in_array(['one', 'two'])
end
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
it 'fails when used in the negative' do
assertion = lambda do
expect(validating_exclusion(in: %w(one two))).
not_to validate_exclusion_of(:attr).in_array(%w(one two))
end
message = <<-MESSAGE
Expected Example not to validate that :attr is neither "one" nor
"two", but this could not be proved.
After setting :attr to "two", the matcher expected the Example to be
valid, but it was invalid instead, producing these validation errors:
* attr: ["is reserved"]
MESSAGE
expect(&assertion).to fail_with_message(message)
end
def define_model_validating_exclusion(options)
options = options.dup
column_type = options.delete(:column_type) { :string }
super options.merge(column_type: column_type)
end
end
def define_model_validating_exclusion(options)
options = options.dup
attribute_name = options.delete(:attribute_name) { :attr }
column_type = options.delete(:column_type) { :integer }
define_model(:example, attribute_name => column_type) do |model|
model.validates_exclusion_of(attribute_name, options)
2012-08-19 09:07:05 +00:00
end
2012-12-20 05:04:27 +00:00
end
2012-08-19 09:07:05 +00:00
2012-12-20 05:04:27 +00:00
def validating_exclusion(options)
define_model_validating_exclusion(options).new
end
alias_method :build_record_validating_exclusion, :validating_exclusion
def validation_matcher_scenario_args
super.deep_merge(matcher_name: :validate_exclusion_of)
2012-08-19 09:07:05 +00:00
end
2011-10-16 17:49:58 +00:00
end