The fact that change failure messages for bulky objects like arrays of hashes or just large hashes has griped me for a while and today I decided to use my pre-vacation day when tying up loose ends to see if I can override RSpec's built-in failure message for change matcher to be more readable.
Built-in:
expected `object.data` to have changed to { a: 1 } but, is now { b: 2 }
Desired (multi-line, values aligned):
expected `object.data` to have changed, but the value did not match expectation:
expected: { a: 1 }
actual: { b: 2 }
This turned out to be a bit more involved than monkeypatching a single message method. When you use a compound change {}.and(change {}) structure, RSpec uses RSpec::Matchers::BuiltIn::Compound::And matcher, instead of RSpec::Matchers::BuiltIn::Change, so overriding in several places is necessary.
It can probably be achieved more cleanly by someone more familiar with RSpec's architecture and codebase, but I achieved my goal with this code:
# place in some /spec/support/respec_change_message_patch.rb
CHANGE_FAILURE_REGEX = %r'\Aexpected `(?<subject>[^`]+)` to have changed to (?<expected>.+), but is now (?<actual>.+)\z'm
CHANGE_FAILURE_MESSAGE_IMPROVER = ->(original_message) {
match = original_message.to_s.match(CHANGE_FAILURE_REGEX)
if match.present?
text = <<~TEXT
expected `#{match[:subject]}` to have changed, but the value did not match expectation:
expected: #{match[:expected]}
actual: #{match[:actual]}
TEXT
RSpec::Core::Formatters::ConsoleCodes.wrap(text, :failure)
else
original_message
end
}
RSpec::Matchers::BuiltIn::Change.prepend(
Module.new do
def failure_message
CHANGE_FAILURE_MESSAGE_IMPROVER.call(super)
end
def failure_message_when_negated
CHANGE_FAILURE_MESSAGE_IMPROVER.call(super)
end
end
)
[
RSpec::Matchers::BuiltIn::Compound,
RSpec::Matchers::BuiltIn::Compound::And
].each do |klass|
klass.prepend(
Module.new do
def failure_message
CHANGE_FAILURE_MESSAGE_IMPROVER.call(super)
end
def compound_failure_message
CHANGE_FAILURE_MESSAGE_IMPROVER.call(super)
end
end
)
end
Here's to hoping I get this merged into RSpec at some point. 🍻
Top comments (0)