Hi Nick,

I came up with a hacky solution that might do the trick for your use case.

```
module MyMatchers
  class Pass
    include RSpec::Matchers::Composable

    def initialize(expected_return_value)
      @expected_return_value = expected_return_value
    end

    def description
      "pass with #{@expected_return_value} return value"
    end

    def matches?(block)
      if block.source_location.first.end_with?('compound.rb')
        fail "You should use this matcher at the top of the chain,
immediately following the `expect { ... }`"
      end

      @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

  def pass(expected_return_value)
    Pass.new(expected_return_value)
  end
end

RSpec.configure do |config|
  config.include MyMatchers
end

$foo = 0
```

Now, when you run:
```
RSpec.describe "a custom block matcher" do
  specify do
    expect { $foo = 2 }
      .to pass(2)
      .and change { $foo }.by(2)
  end
end
```
it will be green.

However, if `pass` is not the first matcher, it will blow up:
```
RSpec.describe "a custom block matcher" do
  specify do
    expect { $foo = 2 }
      .to change { $foo }.by(2)
      .and pass(2)
  end
end
```
with:
```
a custom block matcher
  is expected to change `$foo` by 2 and pass with 2 return value (FAILED -
1)

Failures:

  1) a custom block matcher is expected to change `$foo` by 2 and pass with
2 return value
     Failure/Error: fail "You should use this matcher at the top of the
chain, immediately following the `expect { ... }`"

     RuntimeError:
       You should use this matcher at the top of the chain, immediately
following the `expect { ... }`
 ```

I hope that works.

There's room to make this generic by:
 - wrapping the initial expect block into a proc that would save the return
value of a wrapped initial block into a variable
 - pass the return value to other matchers along with the block around in
NestedEvaluator if matcher's `matches?` accepts two arguments

This would allow for using `pass` in any position in the chain.
I'm still not entirely convinced it will be widely used, since the `expect
{ expect(action!).to eq(return_value) }.to check_side_effect }` trick works
quite fine for non-DSL cases.

- 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 rspec+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/rspec/CAAk5Ok9HOT%3Dp3MNtSV5nmhTsQnLpw7hHq%2Bbu2JEi84rwsF7Fsw%40mail.gmail.com.

Reply via email to