[rspec-users] Applying an rspec matcher against the elements of a collection

Pat Maddox mailinglists at patmaddox.com
Tue Feb 23 16:43:02 EST 2010


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

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

On Feb 23, 2010, at 10:18 AM, Pete Hodgson wrote:

> 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
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users



More information about the rspec-users mailing list