Reputation: 15205
How can a create the following RSpec matcher?
foo.bars.should incude_at_least_one {|bar| bar.id == 42 }
Let me know if I'm reinventing the wheel, but I'm also curious to know how to create a custom matcher that takes a block. Some of the built in matchers do it, so it's possible. I tried this:
RSpec::Matchers.define :incude_at_least_one do |expected|
match do |actual|
actual.each do |item|
return true if yield(item)
end
false
end
end
I aslo tried passing &block
at both leves. I'm missing something simple.
Upvotes: 5
Views: 1043
Reputation: 822
For anyone who comes here for the answer, you should use block_arg
. Here is the snippet from the RSpec documentation:
require 'rspec/expectations'
RSpec::Matchers.define :be_lazily_equal_to do
match do |obj|
obj == block_arg.call
end
description { "be lazily equal to #{block_arg.call}" }
end
RSpec.describe 10 do
it { is_expected.to be_lazily_equal_to { 10 } }
end
Upvotes: 0
Reputation: 15205
I started with the code from Neil Slater, and got it to work:
class IncludeAtLeastOne
def initialize(&block)
@block = block
end
def matches?(actual)
@actual = actual
@actual.any? {|item| @block.call(item) }
end
def failure_message_for_should
"expected #{@actual.inspect} to include at least one matching item, but it did not"
end
def failure_message_for_should_not
"expected #{@actual.inspect} not to include at least one, but it did"
end
end
def include_at_least_one(&block)
IncludeAtLeastOne.new &block
end
Upvotes: 1
Reputation: 27207
The RSpec DSL won't do it, but you could do something like this:
class IncludeAtLeastOne
def matches?(target)
@target = target
@target.any? do |item|
yield( item )
end
end
def failure_message_for_should
"expected #{@target.inspect} to include at least one thing"
end
def failure_message_for_should_not
"expected #{@target.inspect} not to include at least one"
end
end
def include_at_least_one
IncludeAtLeastOne.new
end
describe "foos" do
it "should contain something interesting" do
[1,2,3].should include_at_least_one { |x| x == 1 }
end
end
Upvotes: 0
Reputation: 6856
There has been discussion about adding such a matcher to rspec. I am not sure about your block question but you could represent this test in the not as elegant looking:
foo.bars.any?{|bar| bar.id == 42}.should be_true
Probably easier than making a custom matcher and should be readable if your test is something like it "should include at least one foo matching the id"
Upvotes: 0