Pete Hodgson
2010-Feb-23 18:18 UTC
[rspec-users] 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
Pat Maddox
2010-Feb-23 21:43 UTC
[rspec-users] Applying an rspec matcher against the elements of a collection
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
Pete Hodgson
2010-Feb-23 22:53 UTC
[rspec-users] Applying an rspec matcher against the elements of a collection
Pat, Thanks for the response. On Feb 23, 1:43?pm, Pat Maddox <mailingli... at patmaddox.com> wrote:> I''m going to argue that your design is off, and then ignore the rest of your post :)Fair enough :) 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... :) Cheers, Pete
Michael Guterl
2010-Feb-24 01:26 UTC
[rspec-users] Applying an rspec matcher against the elements of a collection
On Tue, Feb 23, 2010 at 4:43 PM, Pat Maddox <mailinglists at patmaddox.com> wrote: <snip>> > 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 Guterl
Pat Maddox
2010-Feb-24 15:51 UTC
[rspec-users] Applying an rspec matcher against the elements of a collection
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 Feb 23, 2010, at 2:53 PM, Pete Hodgson wrote:> Pat, Thanks for the response. > > On Feb 23, 1:43 pm, Pat Maddox <mailingli... at patmaddox.com> wrote: >> I''m going to argue that your design is off, and then ignore the rest of your post :) > > Fair enough :) 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... :) > > Cheers, > Pete > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users