Applying an rspec matcher against the elements of a collection

Hi all,
I’ve tried to figure out whether rspec has any features to make it
easier to make assertions against the elements of a collection, but I
haven’t had any luck finding anything so far. I thought I’d explain
the problem here, and propose a potential feature that might mitigate
it.

Let’s say I have a Person class:

class Person < Struct.new( :name, :age )

VOTING_AGE = 18
def self.get_voters( people )
people.reject{ |person| person.age < VOTING_AGE }
end

end

As you can see we have a method here which filters a collection of
people, returning only those people old enough to vote. If I were to
test this method in rspec I might write:

describe ‘Person vote filtering’ do
it ‘filters out people younger than voting age’ do
people = [
Person.new( ‘jenny’, 18 ),
Person.new( ‘dave’, 12 ),
Person.new( ‘paul’, 19 ),
Person.new( ‘lisa’, 17 )
]

voters = Person.get_voters( people )

voter_names = voters.map{ |p| p.name }
voter_names.should == ['jenny','paul']

end
end

This works, but having to manually pull out the voter names into a
seperate collection just in order to check who was filtered and who
wasn’t has always seemed clunky to me. What I would prefer is to be
able to check whether the collection contains person who matching my
expectations. Say I have a custom matcher:

Spec::Matchers.define :be_named do |expected|
match do |actual|
actual.name == expected
end
end

Then I’d like to be able to write something like

voters.should( have(2).people )
voters.should( have_one_that( be_named(‘jenny’) ) )
voters.should( have_one_that( be_named(‘paul’) ) )

or even:

voters.should( have_elements_that(
be_named( ‘jenny’ ),
be_named( ‘paul’ )
)

To me this is a lot clearer - although the method names and how
they’re composed into the DSL could clearly use some work ;).

Now, for the trivial case I’ve been using as an example it would
probably be overkill, but I often find myself writing fairly
convuluted code at the end of a test just to figure out whether a
collection contains an element that matches some complex predicate. It
seems to me that if rspec had the generic ability to apply matchers to
the elements of a collection it would raise the level of
expressiveness for this kind of tests.

Thoughts? Does Rspec already support something like this that I’m just
not aware of? If I were to write a patch implementing this would it
have any chance of being accepted?

Cheers,
Pete

I’m going to argue that your design is off, and then ignore the rest of
your post :slight_smile:

class Person < Struct.new(:name, :age)
VOTING_AGE = 18

def voter?
age >= VOTING_AGE
end
end

Now your tests become very simple:

Person.new(‘Jenny’, 17).should_not be_voter
Person.new(‘Bob’, 18).should be_voter

Why you want a Person.get_voters method to select voters from a list,
I’m not really sure. You can always just do:

voters = collection_of_people.select {|p| p.voter?}

Also, RSpec has two mechanisms for testing collections the way you want
(so I guess I’m not ignoring your post after all).

If you only care about inclusion, you can use the include matcher:

jenny = Person.new(‘Jenny’, 17)
bob = Person.new(‘Bob’, 18)
sally = Person.new(‘Sally’, 20)

voters = Person.get_voters(jenny, bob, sally)
voters.should include(bob, sally)
voters.should_not include(jenny)

there is also the set equality matcher, which checks that the contents
of two collections are equal irrespective of order:

voters.should =~ [bob, sally]

Pat

Pat, Thanks for the response.

On Feb 23, 1:43 pm, Pat M. [email protected] wrote:

I’m going to argue that your design is off, and then ignore the rest of your post :slight_smile:

Fair enough :slight_smile: The ‘design’ in my example was made up on the spot to
try and illustrate the kind of issues I’ve been coming up against
without lots of irrelevant detail. I agree that the code has all sorts
of silly issues.

Also, RSpec has two mechanisms for testing collections the way you want (so I guess I’m not ignoring your post after all).

Thanks for pointing me towards the include and =~ matchers. This was
part of what I was looking for, and would solve a lot of the issues
I’ve hit in the past.

That said, I still feel that it would be helpful to have some way of
applying a matcher against the elements of a collection. I will just
have to come up with a more plausible example… :slight_smile:

Cheers,
Pete

I missed one other one that you’ll find handy.

voters.should have(2).items

Take a look at
http://rspec.rubyforge.org/rspec/1.3.0/classes/Spec/Matchers.html
because there’s a lot of useful stuff…

On Tue, Feb 23, 2010 at 4:43 PM, Pat M. [email protected]
wrote:

there is also the set equality matcher, which checks that the contents of two collections are equal irrespective of order:

voters.should =~ [bob, sally]

I can’t believe I didn’t know =~ could be used for comparing
collections regardless of order, this is awesome!

Thanks,
Michael G.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs