1
0
Fork 0
mirror of https://github.com/thoughtbot/shoulda-matchers.git synced 2022-11-09 12:01:38 -05:00

Refactor allow_value & disallow_value

This is part of a collection of commits that aim to improve failure
messages across the board, in order to make matchers easier to debug
when something goes wrong.

* Have the failure message describe more clearly what the `allow_value`
  matcher was trying to do when it failed.
* Make the description of the matcher more readable.
* For each value that `allow_value` sets, use a different Validator
  instance. The matcher still changes state as it runs, but a future
  commit will refactor this further.
* Merge StrictValidator back into Validator, and remove it. The way that
  StrictValidator worked (as a module that was mixed into an instance of
  Validator at runtime) was confusing, and there's really no need to
  split out the logic anymore.
* Fix or fill in tests involving failure messages and descriptions.
This commit is contained in:
Elliot Winkler 2015-12-13 16:55:34 -07:00
parent 8361f39579
commit 583be384c3
7 changed files with 517 additions and 290 deletions

View file

@ -2,7 +2,6 @@ require 'shoulda/matchers/active_model/helpers'
require 'shoulda/matchers/active_model/validation_matcher' require 'shoulda/matchers/active_model/validation_matcher'
require 'shoulda/matchers/active_model/validation_matcher/build_description' require 'shoulda/matchers/active_model/validation_matcher/build_description'
require 'shoulda/matchers/active_model/validator' require 'shoulda/matchers/active_model/validator'
require 'shoulda/matchers/active_model/strict_validator'
require 'shoulda/matchers/active_model/allow_value_matcher' require 'shoulda/matchers/active_model/allow_value_matcher'
require 'shoulda/matchers/active_model/disallow_value_matcher' require 'shoulda/matchers/active_model/disallow_value_matcher'
require 'shoulda/matchers/active_model/validate_length_of_matcher' require 'shoulda/matchers/active_model/validate_length_of_matcher'

View file

@ -354,99 +354,194 @@ https://github.com/thoughtbot/shoulda-matchers/issues
include Helpers include Helpers
attr_accessor :attribute_with_message attr_accessor :failure_message_preface
attr_accessor :options attr_reader :last_value_set
def initialize(*values) def initialize(*values)
self.values_to_match = values @values_to_set = values
self.options = {} @options = {}
self.after_setting_value_callback = -> {} @after_setting_value_callback = -> {}
self.validator = Validator.new
@ignoring_interference_by_writer = false @ignoring_interference_by_writer = false
@expects_strict = false
@expects_custom_validation_message = false
@context = nil
@failure_message_preface = proc do
<<-PREFIX.strip_heredoc.strip
After setting :#{attribute_to_set} to #{last_value_set.inspect},
the matcher expected the #{model.name} to be
PREFIX
end
end end
def for(attribute) def for(attribute)
self.attribute_to_set = attribute @attribute_to_set = attribute
self.attribute_to_check_message_against = attribute @attribute_to_check_message_against = attribute
self self
end end
def on(context) def on(context)
validator.context = context if context.present?
self @context = context
end
def with_message(message, options={})
self.options[:expected_message] = message
self.options[:expected_message_values] = options.fetch(:values, {})
if options.key?(:against)
self.attribute_to_check_message_against = options[:against]
end end
self self
end end
def strict def with_message(message, given_options = {})
validator.strict = true if message.present?
@expects_custom_validation_message = true
options[:expected_message] = message
options[:expected_message_values] = given_options.fetch(:values, {})
if given_options.key?(:against)
@attribute_to_check_message_against = given_options[:against]
end
end
self self
end end
def expects_custom_validation_message?
@expects_custom_validation_message
end
def strict(expects_strict = true)
@expects_strict = expects_strict
self
end
def expects_strict?
@expects_strict
end
def ignoring_interference_by_writer def ignoring_interference_by_writer
@ignoring_interference_by_writer = true @ignoring_interference_by_writer = true
self self
end end
def _after_setting_value(&callback) def _after_setting_value(&callback)
self.after_setting_value_callback = callback @after_setting_value_callback = callback
end end
def matches?(instance) def matches?(instance)
self.instance = instance @instance = instance
values_to_match.all? { |value| value_matches?(value) } first_failing_value_and_validator.nil?
end end
def does_not_match?(instance) def does_not_match?(instance)
self.instance = instance @instance = instance
values_to_match.all? { |value| !value_matches?(value) } first_passing_value_and_validator.nil?
end end
def failure_message def failure_message
"Did not expect #{expectation},\ngot#{error_description}" validator = first_failing_validator
message =
failure_message_preface.call +
' valid, but it was invalid instead,'
if validator.captured_validation_exception?
message << ' raising a validation exception with the message '
message << validator.validation_exception_message.inspect
message << '.'
else
message << " producing these validation errors:\n\n"
message << validator.all_formatted_validation_error_messages
end
Shoulda::Matchers.word_wrap(message)
end end
def failure_message_when_negated def failure_message_when_negated
"Expected #{expectation},\ngot#{error_description}" validator = first_passing_validator
message = failure_message_preface.call + ' invalid'
if validator.type_of_message_matched?
if validator.has_messages?
message << ' and to'
if validator.captured_validation_exception?
message << ' raise a validation exception with message'
else
message << ' produce'
if expected_message.is_a?(Regexp)
message << ' a'
else
message << ' the'
end
message << ' validation error'
end
if expected_message.is_a?(Regexp)
message << ' matching'
end
message << " #{expected_message.inspect}"
unless validator.captured_validation_exception?
message << " on :#{attribute_to_check_message_against}"
end
message << '. The record was indeed invalid, but'
if validator.captured_validation_exception?
message << ' the exception message was '
message << validator.validation_exception_message.inspect
message << ' instead.'
else
message << " it produced these validation errors instead:\n\n"
message << validator.all_formatted_validation_error_messages
end
else
message << ', but it was valid instead.'
end
elsif validator.captured_validation_exception?
message << ' and to produce validation errors, but the record'
message << ' raised a validation exception instead.'
else
message << ' and to raise a validation exception, but the record'
message << ' produced validation errors instead.'
end
Shoulda::Matchers.word_wrap(message)
end
def simple_description
"allow :#{attribute_to_set} to be #{inspected_values_to_set}"
end end
def description def description
validator.allow_description(allowed_values) ValidationMatcher::BuildDescription.call(self, simple_description)
end
def model
instance.class
end end
protected protected
attr_reader :instance, :attribute_to_check_message_against attr_reader(
attr_accessor :values_to_match, :attribute_to_set, :value, :after_setting_value_callback,
:matched_error, :after_setting_value_callback, :validator :attribute_to_check_message_against,
:attribute_to_set,
:context,
:instance,
:options,
:values_to_set,
)
def instance=(instance) private
@instance = instance
validator.record = instance
end
def attribute_to_check_message_against=(attribute)
@attribute_to_check_message_against = attribute
validator.attribute = attribute
end
def ignoring_interference_by_writer? def ignoring_interference_by_writer?
@ignoring_interference_by_writer @ignoring_interference_by_writer
end end
def value_matches?(value) def value_matches?(value, validator)
self.value = value @last_value_set = value
set_attribute(value) set_attribute(value)
!(errors_match? || any_range_error_occurred?) !errors_match?(validator) && !any_range_error_occurred?(validator)
end end
def set_attribute(value) def set_attribute(value)
@ -468,64 +563,36 @@ https://github.com/thoughtbot/shoulda-matchers/issues
end end
end end
def errors_match? def errors_match?(validator)
has_messages? && errors_for_attribute_match? validator.has_messages? &&
validator.type_of_message_matched? &&
errors_for_attribute_match?(validator)
end end
def has_messages? def errors_for_attribute_match?(validator)
validator.has_messages? matched_errors(validator).compact.any?
end end
def errors_for_attribute_match? def matched_errors(validator)
if expected_message if expected_message
self.matched_error = errors_match_regexp? || errors_match_string? validator.messages.grep(expected_message)
else else
errors_for_attribute.compact.any? validator.messages
end end
end end
def errors_for_attribute def any_range_error_occurred?(validator)
validator.formatted_messages
end
def errors_match_regexp?
if Regexp === expected_message
errors_for_attribute.detect { |e| e =~ expected_message }
end
end
def errors_match_string?
if errors_for_attribute.include?(expected_message)
expected_message
end
end
def any_range_error_occurred?
validator.captured_range_error? validator.captured_range_error?
end end
def expectation def inspected_values_to_set
parts = [ if values_to_set.size > 1
expected_messages_description, values_to_set.map(&:inspect).to_sentence(
"when #{attribute_to_set} is set to #{value.inspect}" two_words_connector: " or ",
] last_word_connector: ", or"
)
parts.join(' ').squeeze(' ')
end
def expected_messages_description
validator.expected_messages_description(expected_message)
end
def error_description
validator.messages_description
end
def allowed_values
if values_to_match.length > 1
"any of [#{values_to_match.map(&:inspect).join(', ')}]"
else else
values_to_match.first.inspect values_to_set.first.inspect
end end
end end
@ -540,7 +607,11 @@ https://github.com/thoughtbot/shoulda-matchers/issues
end end
def default_expected_message def default_expected_message
validator.expected_message_from(default_attribute_message) if expects_strict?
"#{human_attribute_name} #{default_attribute_message}"
else
default_attribute_message
end
end end
def default_attribute_message def default_attribute_message
@ -560,9 +631,51 @@ https://github.com/thoughtbot/shoulda-matchers/issues
defaults.merge(options[:expected_message_values]) defaults.merge(options[:expected_message_values])
end end
def values_and_validators
@_values_and_validators ||= values_to_set.map do |value|
[value, build_validator]
end
end
def build_validator
validator = Validator.new(
instance,
attribute_to_check_message_against
)
validator.context = context
validator.expects_strict = expects_strict?
validator
end
def first_failing_value_and_validator
@_first_failing_value_and_validator ||=
values_and_validators.detect do |value, validator|
!value_matches?(value, validator)
end
end
def first_failing_validator
first_failing_value_and_validator[1]
end
def first_passing_value_and_validator
@_first_passing_value_and_validator ||=
values_and_validators.detect do |value, validator|
value_matches?(value, validator)
end
end
def first_passing_validator
first_passing_value_and_validator[1]
end
def model_name def model_name
instance.class.to_s.underscore instance.class.to_s.underscore
end end
def human_attribute_name
instance.class.human_attribute_name(attribute_to_check_message_against)
end
end end
end end
end end

View file

@ -7,46 +7,55 @@ module Shoulda
class DisallowValueMatcher class DisallowValueMatcher
extend Forwardable extend Forwardable
def_delegators :allow_matcher, :_after_setting_value def_delegators :allow_matcher,
:_after_setting_value,
:attribute_to_set,
:description,
:model,
:last_value_set,
:simple_description,
:failure_message_preface,
:failure_message_preface=
def initialize(value) def initialize(value)
@allow_matcher = AllowValueMatcher.new(value) @allow_matcher = AllowValueMatcher.new(value)
end end
def matches?(subject) def matches?(subject)
!@allow_matcher.matches?(subject) !allow_matcher.matches?(subject)
end end
def for(attribute) def for(attribute)
@allow_matcher.for(attribute) allow_matcher.for(attribute)
self self
end end
def on(context) def on(context)
@allow_matcher.on(context) allow_matcher.on(context)
self self
end end
def with_message(message, options={}) def with_message(message, options={})
@allow_matcher.with_message(message, options) allow_matcher.with_message(message, options)
self
end
def strict(strict = true)
allow_matcher.strict(strict)
self self
end end
def ignoring_interference_by_writer def ignoring_interference_by_writer
@allow_matcher.ignoring_interference_by_writer allow_matcher.ignoring_interference_by_writer
self self
end end
def failure_message def failure_message
@allow_matcher.failure_message_when_negated allow_matcher.failure_message_when_negated
end end
def failure_message_when_negated def failure_message_when_negated
@allow_matcher.failure_message allow_matcher.failure_message
end
def strict
@allow_matcher.strict
self
end end
protected protected

View file

@ -3,25 +3,17 @@ module Shoulda
module ActiveModel module ActiveModel
# @private # @private
module Helpers module Helpers
def pretty_error_messages(obj) def pretty_error_messages(object)
obj.errors.map do |attribute, message| format_validation_errors(object.errors)
full_message = message.dup.inspect end
parenthetical_parts = []
unless attribute.to_sym == :base def format_validation_errors(errors)
parenthetical_parts << "attribute: #{attribute}" list_items = errors.keys.map do |attribute|
messages = errors[attribute]
"* #{attribute}: #{messages}"
end
if obj.respond_to?(attribute) list_items.join("\n")
parenthetical_parts << "value: #{obj.__send__(attribute).inspect}"
end
end
if parenthetical_parts.any?
full_message << " (#{parenthetical_parts.join(', ')})"
end
"* " + full_message
end.join("\n")
end end
def default_error_message(type, options = {}) def default_error_message(type, options = {})

View file

@ -1,51 +0,0 @@
module Shoulda
module Matchers
module ActiveModel
# @private
module StrictValidator
def allow_description(allowed_values)
"doesn't raise when #{attribute} is set to #{allowed_values}"
end
def expected_message_from(attribute_message)
"#{human_attribute_name} #{attribute_message}"
end
def formatted_messages
[messages.first.message]
end
def messages_description
if has_messages?
': ' + messages.first.message.inspect
else
' no exception'
end
end
def expected_messages_description(expected_message)
if expected_message
"exception to include #{expected_message.inspect}"
else
'an exception to have been raised'
end
end
protected
def collect_messages
validation_exceptions
end
private
def validation_exceptions
record.valid?(context)
[]
rescue ::ActiveModel::StrictValidationFailed => exception
[exception]
end
end
end
end
end

View file

@ -5,99 +5,104 @@ module Shoulda
class Validator class Validator
include Helpers include Helpers
attr_writer :attribute, :context, :record attr_writer :context, :expects_strict
def initialize def initialize(record, attribute)
@record = record
@attribute = attribute
@context = context
@expects_strict = false
reset reset
end end
def reset def reset
@messages = nil @_validation_result = nil
end @captured_validation_exception = false
@captured_range_error = false
def strict=(strict)
@strict = strict
if strict
extend StrictValidator
end
end
def allow_description(allowed_values)
"allow #{attribute} to be set to #{allowed_values}"
end
def expected_message_from(attribute_message)
attribute_message
end end
def messages def messages
@messages ||= collect_messages if expects_strict?
end [validation_exception_message]
else
def formatted_messages validation_error_messages
messages end
end end
def has_messages? def has_messages?
messages.any? messages.any?
end end
def messages_description def type_of_message_matched?
if has_messages? expects_strict? == captured_validation_exception?
" errors:\n#{pretty_error_messages(record)}"
else
' no errors'
end
end end
def expected_messages_description(expected_message) def captured_validation_exception?
if expected_message @captured_validation_exception
"errors to include #{expected_message.inspect}"
else
'errors'
end
end end
def captured_range_error? def captured_range_error?
!!captured_range_error !!@captured_range_error
end
def all_validation_errors
validation_result[:all_validation_errors]
end
def all_formatted_validation_error_messages
format_validation_errors(all_validation_errors)
end
def validation_error_messages
validation_result[:validation_error_messages]
end
def validation_exception_message
validation_result[:validation_exception_message]
end end
protected protected
attr_reader :attribute, :context, :strict, :record, attr_reader :attribute, :context, :record
:captured_range_error
def collect_messages
validation_errors
end
private private
def strict? def expects_strict?
!!@strict @expects_strict
end end
def collect_errors_or_exceptions def validation_result
collect_messages @_validation_result ||= perform_validation
end end
def validation_errors def perform_validation
if context if context
record.valid?(context) record.valid?(context)
else else
record.valid? record.valid?
end end
if record.errors.respond_to?(:[]) all_validation_errors = record.errors.dup
record.errors[attribute]
else
record.errors.on(attribute)
end
end
def human_attribute_name validation_error_messages =
record.class.human_attribute_name(attribute) if record.errors.respond_to?(:[])
record.errors[attribute]
else
record.errors.on(attribute)
end
{
all_validation_errors: all_validation_errors,
validation_error_messages: validation_error_messages,
validation_exception_message: nil
}
rescue ::ActiveModel::StrictValidationFailed => exception
@captured_validation_exception = true
{
all_validation_errors: nil,
validation_error_messages: [],
validation_exception_message: exception.message
}
end end
end end
end end

View file

@ -13,21 +13,24 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
it 'describes itself with multiple values' do it 'describes itself with multiple values' do
matcher = allow_value('foo', 'bar').for(:baz) matcher = allow_value('foo', 'bar').for(:baz)
expect(matcher.description).to eq 'allow baz to be set to any of ["foo", "bar"]' expect(matcher.description).to eq(
'allow :baz to be "foo" or "bar"'
)
end end
it 'describes itself with a single value' do it 'describes itself with a single value' do
matcher = allow_value('foo').for(:baz) matcher = allow_value('foo').for(:baz)
expect(matcher.description).to eq 'allow baz to be set to "foo"' expect(matcher.description).to eq 'allow :baz to be "foo"'
end end
if active_model_3_2? if active_model_3_2?
it 'describes itself with a strict validation' do it 'describes itself with a strict validation' do
strict_matcher = allow_value('xyz').for(:attr).strict strict_matcher = allow_value('xyz').for(:attr).strict
expect(strict_matcher.description). expect(strict_matcher.description).to eq(
to eq %q(doesn't raise when attr is set to "xyz") 'allow :attr to be "xyz", raising a validation exception on failure'
)
end end
end end
end end
@ -51,8 +54,19 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
expect(validating_format(with: /abc/)).to allow_value('abcde').for(:attr) expect(validating_format(with: /abc/)).to allow_value('abcde').for(:attr)
end end
it 'rejects a bad value' do it 'rejects a bad value with an appropriate failure message' do
expect(validating_format(with: /abc/)).not_to allow_value('xyz').for(:attr) message = <<-MESSAGE
After setting :attr to "xyz", the matcher expected the Example to be
valid, but it was invalid instead, producing these validation errors:
* attr: ["is invalid"]
MESSAGE
assertion = lambda do
expect(validating_format(with: /abc/)).to allow_value('xyz').for(:attr)
end
expect(&assertion).to fail_with_message(message)
end end
it 'allows several good values' do it 'allows several good values' do
@ -60,11 +74,31 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
to allow_value('abcde', 'deabc').for(:attr) to allow_value('abcde', 'deabc').for(:attr)
end end
it 'rejects several bad values' do context 'given several bad values' do
expect(validating_format(with: /abc/)). it 'rejects' do
not_to allow_value('xyz', 'zyx', nil, []). expect(validating_format(with: /abc/)).
for(:attr). not_to allow_value('xyz', 'zyx', nil, []).
ignoring_interference_by_writer for(:attr).
ignoring_interference_by_writer
end
it 'produces an appropriate failure message' do
message = <<-MESSAGE
After setting :attr to "zyx", the matcher expected the Example to be
valid, but it was invalid instead, producing these validation errors:
* attr: ["is invalid"]
MESSAGE
assertion = lambda do
expect(validating_format(with: /abc/)).
to allow_value('abc', 'abcde', 'zyx', nil, []).
for(:attr).
ignoring_interference_by_writer
end
expect(&assertion).to fail_with_message(message)
end
end end
end end
@ -74,27 +108,95 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
to allow_value('abcde').for(:attr).with_message(/bad/) to allow_value('abcde').for(:attr).with_message(/bad/)
end end
it 'rejects a bad value' do it 'rejects a bad value with an appropriate failure message' do
expect(validating_format(with: /abc/, message: 'bad value')). message = <<-MESSAGE
not_to allow_value('xyz').for(:attr).with_message(/bad/) After setting :attr to "xyz", the matcher expected the Example to be
valid, but it was invalid instead, producing these validation errors:
* attr: ["bad value"]
MESSAGE
assertion = lambda do
expect(validating_format(with: /abc/, message: 'bad value')).
to allow_value('xyz').for(:attr).with_message(/bad/)
end
expect(&assertion).to fail_with_message(message)
end end
it 'allows interpolation values for the message to be provided' do context 'when the custom messages do not match' do
options = { it 'rejects with an appropriate failure message' do
attribute_name: :attr, message = <<-MESSAGE
attribute_type: :string After setting :attr to "xyz", the matcher expected the Example to be
} invalid and to produce a validation error matching /different/ on :attr.
The record was indeed invalid, but it produced these validation errors
instead:
record = record_with_custom_validation(options) do * attr: ["bad value"]
if self.attr == 'xyz' MESSAGE
self.errors.add :attr, :greater_than, count: 2
assertion = lambda do
expect(validating_format(with: /abc/, message: 'bad value')).
not_to allow_value('xyz').for(:attr).with_message(/different/)
end
expect(&assertion).to fail_with_message(message)
end
end
context 'when interpolation values are provided along with a custom message' do
context 'when the messages match' do
it 'accepts' do
options = {
attribute_name: :attr,
attribute_type: :string
}
record = record_with_custom_validation(options) do
if self.attr == 'xyz'
self.errors.add :attr, :greater_than, count: 2
end
end
expect(record).
not_to allow_value('xyz').
for(:attr).
with_message(:greater_than, values: { count: 2 })
end end
end end
expect(record). context 'when the messages do not match' do
not_to allow_value('xyz'). it 'rejects with an appropriate failure message' do
for(:attr). options = {
with_message(:greater_than, values: { count: 2 }) attribute_name: :attr,
attribute_type: :string
}
record = record_with_custom_validation(options) do
if self.attr == 'xyz'
self.errors.add :attr, "some other error"
end
end
assertion = lambda do
expect(record).
not_to allow_value('xyz').
for(:attr).
with_message(:greater_than, values: { count: 2 })
end
message = <<-MESSAGE
After setting :attr to "xyz", the matcher expected the Example to be
invalid and to produce the validation error "must be greater than 2" on
:attr. The record was indeed invalid, but it produced these validation
errors instead:
* attr: ["some other error"]
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
end end
end end
@ -102,25 +204,62 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
include UnitTests::AllowValueMatcherHelpers include UnitTests::AllowValueMatcherHelpers
context 'when the validation error message was provided directly' do context 'when the validation error message was provided directly' do
it 'passes given a valid value' do context 'given a valid value' do
builder = builder_for_record_with_different_error_attribute it 'accepts' do
expect(builder.record). builder = builder_for_record_with_different_error_attribute
to allow_value(builder.valid_value). expect(builder.record).
for(builder.attribute_to_validate). to allow_value(builder.valid_value).
with_message(builder.message, for(builder.attribute_to_validate).
against: builder.attribute_that_receives_error with_message(
) builder.message,
against: builder.attribute_that_receives_error
)
end
end end
it 'fails given an invalid value' do context 'given an invalid value' do
builder = builder_for_record_with_different_error_attribute it 'rejects' do
invalid_value = "#{builder.valid_value} (invalid)" builder = builder_for_record_with_different_error_attribute
expect(builder.record). invalid_value = "#{builder.valid_value} (invalid)"
not_to allow_value(invalid_value).
for(builder.attribute_to_validate). expect(builder.record).
with_message(builder.message, not_to allow_value(invalid_value).
against: builder.attribute_that_receives_error for(builder.attribute_to_validate).
) with_message(
builder.message,
against: builder.attribute_that_receives_error
)
end
context 'if the messages do not match' do
it 'technically accepts' do
builder = builder_for_record_with_different_error_attribute(
message: "a different error"
)
invalid_value = "#{builder.valid_value} (invalid)"
assertion = lambda do
expect(builder.record).
not_to allow_value(invalid_value).
for(builder.attribute_to_validate).
with_message(
"some error",
against: builder.attribute_that_receives_error
)
end
message = <<-MESSAGE
After setting :#{builder.attribute_to_validate} to "#{invalid_value}", the
matcher expected the #{builder.model.name} to be invalid and to produce the validation
error "some error" on :#{builder.attribute_that_receives_error}. The record was
indeed invalid, but it produced these validation errors instead:
* #{builder.attribute_that_receives_error}: ["a different error"]
MESSAGE
expect(&assertion).to fail_with_message(message)
end
end
end end
end end
@ -130,7 +269,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
expect(builder.record). expect(builder.record).
to allow_value(builder.valid_value). to allow_value(builder.valid_value).
for(builder.attribute_to_validate). for(builder.attribute_to_validate).
with_message(builder.validation_message_key, with_message(
builder.validation_message_key,
against: builder.attribute_that_receives_error against: builder.attribute_that_receives_error
) )
end end
@ -141,7 +281,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
expect(builder.record). expect(builder.record).
not_to allow_value(invalid_value). not_to allow_value(invalid_value).
for(builder.attribute_to_validate). for(builder.attribute_to_validate).
with_message(builder.validation_message_key, with_message(
builder.validation_message_key,
against: builder.attribute_that_receives_error against: builder.attribute_that_receives_error
) )
end end
@ -199,11 +340,16 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
end end
it "does not match given good values along with bad values" do it "does not match given good values along with bad values" do
message = %{Expected errors when attr is set to "12345",\ngot no errors} message = <<-MESSAGE.strip_heredoc
After setting :attr to "12345", the matcher expected the Example to be
invalid, but it was valid instead.
MESSAGE
expect { assertion = lambda do
expect(model).not_to allow_value('12345', *bad_values).for(:attr) expect(model).not_to allow_value('12345', *bad_values).for(:attr)
}.to fail_with_message(message) end
expect(&assertion).to fail_with_message(message)
end end
end end
@ -226,25 +372,39 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
if active_model_3_2? if active_model_3_2?
context 'an attribute with a strict format validation' do context 'an attribute with a strict format validation' do
it 'strictly rejects a bad value' do context 'when qualified with strict' do
expect(validating_format(with: /abc/, strict: true)). it 'rejects a bad value, providing the correct failure message' do
not_to allow_value('xyz').for(:attr).strict message = <<-MESSAGE.strip_heredoc
end After setting :attr to "xyz", the matcher expected the Example to be
valid, but it was invalid instead, raising a validation exception with
the message "Attr is invalid".
MESSAGE
it 'strictly allows a bad value with a different message' do assertion = lambda do
expect(validating_format(with: /abc/, strict: true)). expect(validating_format(with: /abc/, strict: true)).
to allow_value('xyz').for(:attr).with_message(/abc/).strict to allow_value('xyz').for(:attr).strict
end end
it 'provides a useful negative failure message' do expect(&assertion).to fail_with_message(message)
matcher = allow_value('xyz').for(:attr).strict.with_message(/abc/) end
matcher.matches?(validating_format(with: /abc/, strict: true)) context 'qualified with a custom message' do
it 'rejects a bad value when the failure messages do not match' do
message = <<-MESSAGE.strip_heredoc
After setting :attr to "xyz", the matcher expected the Example to be
invalid and to raise a validation exception with message matching /abc/.
The record was indeed invalid, but the exception message was "Attr is
invalid" instead.
MESSAGE
expect(matcher.failure_message_when_negated).to eq( assertion = lambda do
%{Expected exception to include /abc/ when attr is set to "xyz",\n} + expect(validating_format(with: /abc/, strict: true)).
%{got: "Attr is invalid"} not_to allow_value('xyz').for(:attr).with_message(/abc/).strict
) end
expect(&assertion).to fail_with_message(message)
end
end
end end
end end
end end