Hi Nick! > #1337
It's still up for the grabs ;) > I'm referring to this issue: https://github.com/rspec/rspec-expectations/issues/1336 > > Basically, I'm trying to implement a matcher that can be chained with > others after an `expect {}` , as in the following snippet. > > ```ruby > expect { run({duration: "2.24"}) } > .to pass > .and change { "yo" }.by(1) > ``` > > When running this, I get this error: > NoMethodError: > undefined method `success?' for #<Proc:0x000055697246eea8 > /home/nick/projects/rspec-trailblazer/spec/expectation_spec.rb:46> > > My matcher, when used in a chain, doesn't receive the `actual` result > object (specific to `run` and our library) but a Proc instance. How would I > write a matcher that supports the block syntax? > I believe the distinction is that block matchers check side effects, while value matchers - returned value. The documentation for building block matchers is here https://relishapp.com/rspec/rspec-expectations/v/3-10/docs/custom-matchers/define-a-matcher-supporting-block-expectations But I believe it's confusing in terms of telling a story how to create a block matcher. Frankly, I can't create a usable composable block matcher using those docs. To add to the confusion, there are actually two ways of creating matchers. The first one is with `RSpec::Matchers.define`, and another one is by writing a regular class. I usually start with `define`, get quickly frustrated and switch to a class. One example is this matcher https://github.com/pirj/rspec-enqueue_sidekiq_job/blob/main/lib/rspec/enqueue_sidekiq_job.rb#L73 But it's not checking the returned value along the way. So I quickly came up with this: ``` $foo = 0 class Bar include RSpec::Matchers::Composable def initialize(expected_return_value) @expected_return_value = expected_return_value end def matches?(block) @actual_return_value = block.call values_match?(@expected_return_value, @actual_return_value) end def failure_message(*args) "Expected: #{@expected_return_value}, got: #{@actual_return_value}" end def supports_block_expectations? true end end module RSpec module Matchers # reopen for simplicity. you can `config.include MyModule` in spec_helper def bar(expected_return_value) Bar.new(expected_return_value) end end end RSpec.describe "a custom block matcher" do specify do expect { $foo = 2 } .to bar(2) .and change { $foo }.by(2) end end ``` and it works! However, if you change the order: ``` expect { $foo = 2 } .to change { $foo }.by(2) .and bar(2) ``` it starts complaining that `@actual_return_value` (the result of the block being called) is `true`, not `2`. It seems to me that only the innermost matcher can get the return value of the block, and it's never passed over to composed matchers. Can you work with that? This would imply that the matcher that checks the return value is the always the first one in the chain. It feels doable to pass the returned value of the block along. We've restricted value matchers from working with block expectations in RSpec 4, i.e. ``` expect { 1 }.to eq(1) ``` will raise an error. But I have no certainty if this feature will not be confusing. Even though it might be usable in some cases, e.g.: ``` expect { post :delete } .to be_ok .and change { User.count }.by(-1) ``` instead of ``` expect { post :delete } .to change { User.count }.by(-1) expect(response).to be_ok ``` Let me know what you think. > happy holidays Happy holidays to you too, Nick! - Phil -- You received this message because you are subscribed to the Google Groups "rspec" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/rspec/CAAk5Ok8-O%3DExHSoib4YHwwn-9_GBLa8_xLxGcutkPk6Zjw-4kQ%40mail.gmail.com.
