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/build_description'
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/disallow_value_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
attr_accessor :attribute_with_message
attr_accessor :options
attr_accessor :failure_message_preface
attr_reader :last_value_set
def initialize(*values)
self.values_to_match = values
self.options = {}
self.after_setting_value_callback = -> {}
self.validator = Validator.new
@values_to_set = values
@options = {}
@after_setting_value_callback = -> {}
@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
def for(attribute)
self.attribute_to_set = attribute
self.attribute_to_check_message_against = attribute
@attribute_to_set = attribute
@attribute_to_check_message_against = attribute
self
end
def on(context)
validator.context = context
self
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]
if context.present?
@context = context
end
self
end
def strict
validator.strict = true
def with_message(message, given_options = {})
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
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
@ignoring_interference_by_writer = true
self
end
def _after_setting_value(&callback)
self.after_setting_value_callback = callback
@after_setting_value_callback = callback
end
def matches?(instance)
self.instance = instance
values_to_match.all? { |value| value_matches?(value) }
@instance = instance
first_failing_value_and_validator.nil?
end
def does_not_match?(instance)
self.instance = instance
values_to_match.all? { |value| !value_matches?(value) }
@instance = instance
first_passing_value_and_validator.nil?
end
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
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
def description
validator.allow_description(allowed_values)
ValidationMatcher::BuildDescription.call(self, simple_description)
end
def model
instance.class
end
protected
attr_reader :instance, :attribute_to_check_message_against
attr_accessor :values_to_match, :attribute_to_set, :value,
:matched_error, :after_setting_value_callback, :validator
attr_reader(
:after_setting_value_callback,
:attribute_to_check_message_against,
:attribute_to_set,
:context,
:instance,
:options,
:values_to_set,
)
def instance=(instance)
@instance = instance
validator.record = instance
end
def attribute_to_check_message_against=(attribute)
@attribute_to_check_message_against = attribute
validator.attribute = attribute
end
private
def ignoring_interference_by_writer?
@ignoring_interference_by_writer
end
def value_matches?(value)
self.value = value
def value_matches?(value, validator)
@last_value_set = value
set_attribute(value)
!(errors_match? || any_range_error_occurred?)
!errors_match?(validator) && !any_range_error_occurred?(validator)
end
def set_attribute(value)
@ -468,64 +563,36 @@ https://github.com/thoughtbot/shoulda-matchers/issues
end
end
def errors_match?
has_messages? && errors_for_attribute_match?
def errors_match?(validator)
validator.has_messages? &&
validator.type_of_message_matched? &&
errors_for_attribute_match?(validator)
end
def has_messages?
validator.has_messages?
def errors_for_attribute_match?(validator)
matched_errors(validator).compact.any?
end
def errors_for_attribute_match?
def matched_errors(validator)
if expected_message
self.matched_error = errors_match_regexp? || errors_match_string?
validator.messages.grep(expected_message)
else
errors_for_attribute.compact.any?
validator.messages
end
end
def errors_for_attribute
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?
def any_range_error_occurred?(validator)
validator.captured_range_error?
end
def expectation
parts = [
expected_messages_description,
"when #{attribute_to_set} is set to #{value.inspect}"
]
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(', ')}]"
def inspected_values_to_set
if values_to_set.size > 1
values_to_set.map(&:inspect).to_sentence(
two_words_connector: " or ",
last_word_connector: ", or"
)
else
values_to_match.first.inspect
values_to_set.first.inspect
end
end
@ -540,7 +607,11 @@ https://github.com/thoughtbot/shoulda-matchers/issues
end
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
def default_attribute_message
@ -560,9 +631,51 @@ https://github.com/thoughtbot/shoulda-matchers/issues
defaults.merge(options[:expected_message_values])
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
instance.class.to_s.underscore
end
def human_attribute_name
instance.class.human_attribute_name(attribute_to_check_message_against)
end
end
end
end

View File

@ -7,46 +7,55 @@ module Shoulda
class DisallowValueMatcher
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)
@allow_matcher = AllowValueMatcher.new(value)
end
def matches?(subject)
!@allow_matcher.matches?(subject)
!allow_matcher.matches?(subject)
end
def for(attribute)
@allow_matcher.for(attribute)
allow_matcher.for(attribute)
self
end
def on(context)
@allow_matcher.on(context)
allow_matcher.on(context)
self
end
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
end
def ignoring_interference_by_writer
@allow_matcher.ignoring_interference_by_writer
allow_matcher.ignoring_interference_by_writer
self
end
def failure_message
@allow_matcher.failure_message_when_negated
allow_matcher.failure_message_when_negated
end
def failure_message_when_negated
@allow_matcher.failure_message
end
def strict
@allow_matcher.strict
self
allow_matcher.failure_message
end
protected

View File

@ -3,25 +3,17 @@ module Shoulda
module ActiveModel
# @private
module Helpers
def pretty_error_messages(obj)
obj.errors.map do |attribute, message|
full_message = message.dup.inspect
parenthetical_parts = []
def pretty_error_messages(object)
format_validation_errors(object.errors)
end
unless attribute.to_sym == :base
parenthetical_parts << "attribute: #{attribute}"
def format_validation_errors(errors)
list_items = errors.keys.map do |attribute|
messages = errors[attribute]
"* #{attribute}: #{messages}"
end
if obj.respond_to?(attribute)
parenthetical_parts << "value: #{obj.__send__(attribute).inspect}"
end
end
if parenthetical_parts.any?
full_message << " (#{parenthetical_parts.join(', ')})"
end
"* " + full_message
end.join("\n")
list_items.join("\n")
end
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
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
end
def reset
@messages = nil
end
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
@_validation_result = nil
@captured_validation_exception = false
@captured_range_error = false
end
def messages
@messages ||= collect_messages
end
def formatted_messages
messages
if expects_strict?
[validation_exception_message]
else
validation_error_messages
end
end
def has_messages?
messages.any?
end
def messages_description
if has_messages?
" errors:\n#{pretty_error_messages(record)}"
else
' no errors'
end
def type_of_message_matched?
expects_strict? == captured_validation_exception?
end
def expected_messages_description(expected_message)
if expected_message
"errors to include #{expected_message.inspect}"
else
'errors'
end
def captured_validation_exception?
@captured_validation_exception
end
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
protected
attr_reader :attribute, :context, :strict, :record,
:captured_range_error
def collect_messages
validation_errors
end
attr_reader :attribute, :context, :record
private
def strict?
!!@strict
def expects_strict?
@expects_strict
end
def collect_errors_or_exceptions
collect_messages
def validation_result
@_validation_result ||= perform_validation
end
def validation_errors
def perform_validation
if context
record.valid?(context)
else
record.valid?
end
if record.errors.respond_to?(:[])
record.errors[attribute]
else
record.errors.on(attribute)
end
end
all_validation_errors = record.errors.dup
def human_attribute_name
record.class.human_attribute_name(attribute)
validation_error_messages =
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

View File

@ -13,21 +13,24 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
it 'describes itself with multiple values' do
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
it 'describes itself with a single value' do
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
if active_model_3_2?
it 'describes itself with a strict validation' do
strict_matcher = allow_value('xyz').for(:attr).strict
expect(strict_matcher.description).
to eq %q(doesn't raise when attr is set to "xyz")
expect(strict_matcher.description).to eq(
'allow :attr to be "xyz", raising a validation exception on failure'
)
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)
end
it 'rejects a bad value' do
expect(validating_format(with: /abc/)).not_to allow_value('xyz').for(:attr)
it 'rejects a bad value with an appropriate failure message' do
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
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)
end
it 'rejects several bad values' do
expect(validating_format(with: /abc/)).
not_to allow_value('xyz', 'zyx', nil, []).
for(:attr).
ignoring_interference_by_writer
context 'given several bad values' do
it 'rejects' do
expect(validating_format(with: /abc/)).
not_to allow_value('xyz', 'zyx', nil, []).
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
@ -74,27 +108,95 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
to allow_value('abcde').for(:attr).with_message(/bad/)
end
it 'rejects a bad value' do
expect(validating_format(with: /abc/, message: 'bad value')).
not_to allow_value('xyz').for(:attr).with_message(/bad/)
it 'rejects a bad value with an appropriate failure message' do
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: ["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
it 'allows interpolation values for the message to be provided' do
options = {
attribute_name: :attr,
attribute_type: :string
}
context 'when the custom messages do not match' do
it 'rejects with an appropriate failure message' do
message = <<-MESSAGE
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
if self.attr == 'xyz'
self.errors.add :attr, :greater_than, count: 2
* attr: ["bad value"]
MESSAGE
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
expect(record).
not_to allow_value('xyz').
for(:attr).
with_message(:greater_than, values: { count: 2 })
context 'when the messages do not match' do
it 'rejects with an appropriate failure message' do
options = {
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
@ -102,25 +204,62 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
include UnitTests::AllowValueMatcherHelpers
context 'when the validation error message was provided directly' do
it 'passes given a valid value' do
builder = builder_for_record_with_different_error_attribute
expect(builder.record).
to allow_value(builder.valid_value).
for(builder.attribute_to_validate).
with_message(builder.message,
against: builder.attribute_that_receives_error
)
context 'given a valid value' do
it 'accepts' do
builder = builder_for_record_with_different_error_attribute
expect(builder.record).
to allow_value(builder.valid_value).
for(builder.attribute_to_validate).
with_message(
builder.message,
against: builder.attribute_that_receives_error
)
end
end
it 'fails given an invalid value' do
builder = builder_for_record_with_different_error_attribute
invalid_value = "#{builder.valid_value} (invalid)"
expect(builder.record).
not_to allow_value(invalid_value).
for(builder.attribute_to_validate).
with_message(builder.message,
against: builder.attribute_that_receives_error
)
context 'given an invalid value' do
it 'rejects' do
builder = builder_for_record_with_different_error_attribute
invalid_value = "#{builder.valid_value} (invalid)"
expect(builder.record).
not_to allow_value(invalid_value).
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
@ -130,7 +269,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
expect(builder.record).
to allow_value(builder.valid_value).
for(builder.attribute_to_validate).
with_message(builder.validation_message_key,
with_message(
builder.validation_message_key,
against: builder.attribute_that_receives_error
)
end
@ -141,7 +281,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
expect(builder.record).
not_to allow_value(invalid_value).
for(builder.attribute_to_validate).
with_message(builder.validation_message_key,
with_message(
builder.validation_message_key,
against: builder.attribute_that_receives_error
)
end
@ -199,11 +340,16 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
end
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)
}.to fail_with_message(message)
end
expect(&assertion).to fail_with_message(message)
end
end
@ -226,25 +372,39 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
if active_model_3_2?
context 'an attribute with a strict format validation' do
it 'strictly rejects a bad value' do
expect(validating_format(with: /abc/, strict: true)).
not_to allow_value('xyz').for(:attr).strict
end
context 'when qualified with strict' do
it 'rejects a bad value, providing the correct failure message' do
message = <<-MESSAGE.strip_heredoc
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
expect(validating_format(with: /abc/, strict: true)).
to allow_value('xyz').for(:attr).with_message(/abc/).strict
end
assertion = lambda do
expect(validating_format(with: /abc/, strict: true)).
to allow_value('xyz').for(:attr).strict
end
it 'provides a useful negative failure message' do
matcher = allow_value('xyz').for(:attr).strict.with_message(/abc/)
expect(&assertion).to fail_with_message(message)
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(
%{Expected exception to include /abc/ when attr is set to "xyz",\n} +
%{got: "Attr is invalid"}
)
assertion = lambda do
expect(validating_format(with: /abc/, strict: true)).
not_to allow_value('xyz').for(:attr).with_message(/abc/).strict
end
expect(&assertion).to fail_with_message(message)
end
end
end
end
end