You could implement your own custom Matcher
for this specific case like:
RSpec::Matchers.define :respond_with do |expected|
match do |actual|
actual.call == expected
end
# allow the matcher to support block expectations
supports_block_expectations
# make sure this executes in the correct context
def expects_call_stack_jump?
true
end
end
Then your expectation would be something like
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
expect{wojit.do_thing}.to change(wojit, :value).and(respond_with(true))
end
The key here is that be
,eq
, etc. does not support block expectations and thus cannot be used in conjuction with expect{...}
so we implemented an equality matcher that does support block expectations (supports_block_expectations? #=> true
) and jumped it up the stack (this is very important in this case otherwise the change block creates a conflicting actual *Not sure I 100% understand why but trust me it does).
In this case actual
will be the block body (as a Proc
) so we just have to call it to compare the result to the expected value.
You could however abstract this out further to something like
RSpec::Matchers.define :have_response do |expectation|
supports_block_expectations
def expects_call_stack_jump?
true
end
#Actual matching logic
match do |actual|
@actual_value = actual.respond_to?(:call) ? actual.call : actual
expect(@actual_value).to(expectation)
end
failure_message do |actual|
"expected response to be #{expectation.expected} but response was #{@actual_value}"
end
failure_message_when_negated do |actual|
"expected response not to be #{expectation.expected} but response was #{@actual_value}"
end
end
#define negation for chaining purposes as needed
RSpec::Matchers.define_negated_matcher :not_have_response, :have_response
Which would allow you to use all the methods that do not support block expectations like so
it "Updates myvalue if condition is met" do
wojit = MyKlass.new
expect{wojit.do_thing}.to change(wojit, :value).and(have_response(be true))
# or
# expect{wojit.do_thing}.to not_have_response(be false).and(change(wojit, :value))
end
Only issue with either one of these approaches is that the block will be called once for the change and once for the response check so depending on your circumstances this could cause issues.