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