Mocking and stubbing model relationships


#1

Before writing specs for a one-to-many relationship between two
models, I did some research and found that some people were creating
proxy mocks, and others were using Matthew Heidemann’s
#stub_association! (which essentially does that for, but in a nice,
DRY way):
http://www.ruby-forum.com/topic/126993

Are those two methods currently the accepted “best practice” for
mocking and stubbing calls such like these?:

@properties = @user.properties
@property = @user.properties.new
@property = @user.properties.find_by_id params[:id]

Thanks,
Nick


#2

On 18 Nov 2008, at 05:41, Nick H. wrote:

Before writing specs for a one-to-many relationship between two
models, I did some research and found that some people were creating
proxy mocks, and others were using Matthew Heidemann’s
#stub_association! (which essentially does that for, but in a nice,
DRY way):
http://www.ruby-forum.com/topic/126993

Are those two methods currently the accepted “best practice” for
mocking and stubbing calls such like these?:

I don’t know about any “best practice”. In the realm of TDD vs
ActiveRecord Associations, you’re looking at something more like
“least-worst” practice, IMO. The way the AssociationCollections behave
is pretty complex and difficult to simulate with a simple mock or two.

I started out trying to stay ‘pure’ and not touch the database, but
TBH, these days I’ve given up the fight and mostly just throw a few
records in a database table - that way you can actually specify the
behaviour you want rather than the gory implementation details.

As you can probably tell by my grumbling, this is one of my least
favourite bits of working on rails.

@properties = @user.properties
@property = @user.properties.new
@property = @user.properties.find_by_id params[:id]

Saying that, it is often still reasonable, I think, to fake an
association proxy collection using an array that’s patched with a few
extra methods. We have a helper method in the Songkick spec code
that’s called something like FakeCollection, which subclasses array
and has a few helpful methods to make it look enough like an
association collection to make the specs run OK.

HTH,
Matt


#3

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 18 Nov 2008, at 05:41, Nick H. wrote:

@properties = @user.properties
@property = @user.properties.new
@property = @user.properties.find_by_id params[:id]

There’s also Luke R.'s Demeter’s Revenge plugin
(http://www.lukeredpath.co.uk/2007/10/18/demeters-revenge
or git cloned to http://github.com/caius/demeters_revenge/tree/
master).

This means you have to change your implementation code as well, but
lets you write:

@properties = @user.properties
@property = @user.build_property # or @user.create_property
@found_properties = @user.find_properties(:conditions => { :some
=> :values })
@user.has_no_properties? # @user.properties.empty?
@user.has_properties? # !@user.properties.empty?
and a few others.

Of course, this also makes it very easy to stub the methods for each
association.

Baz.

Rahoul B.
Web design and development: http://www.3hv.co.uk/
Nottingham Forest: http://www.eighteensixtyfive.co.uk/
Serious Rails Hosting: http://www.brightbox.co.uk/
Lifecast: http://www.madeofstone.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.8 (Darwin)

iEYEARECAAYFAkkitgMACgkQu0BNRvjN8xTmvwCZAZt2nQTv862fHh/8k0rfqW7p
wJIAnAikW7RStJ8OCoV2eWYqs7voz5Py
=tXFK
-----END PGP SIGNATURE-----


#4

I’ve actually taken this old gem and enhanced it a bit

module Spec::Mocks::Methods
def stub_association!(association_name, methods_to_be_stubbed ={})
mock_assn = Spec::Mocks::Mock.new(association_name.to_s)
stub_association_with(association_name, mock_assn,
methods_to_be_stubbed)
end

def stub_association_with(association_name, values,
methods_to_be_stubbed
= {})
methods_to_be_stubbed.each do |meth, return_value|
values.stub!(meth).and_return(return_value)
end
yield(values) if block_given?

self.stub!(association_name).and_return(values)

end
end

This lets me specify the “contents” of an association:

foo.stub_association_with(:bar, [@bar1, @bar2, @bar3], :find => @bar1)

and also gives me some more fine grained control about stubbing the
association

foo.stub_association_with(:bar, [@bar1, @bar2, @bar3]) do |assn|
assn.stub!(:find).with(1).and_return @bar1
assn.stub!(:find).with(2).and_return @bar2
assn.stub!(:find).with(5).and_raise RecordNotFound
end


#5

On 2008-11-18, at 07:33, Rahoul B. wrote:

@properties = @user.properties
@property = @user.properties.new
@property = @user.properties.find_by_id params[:id]

There’s also Luke R.'s Demeter’s Revenge plugin (http://www.lukeredpath.co.uk/2007/10/18/demeters-revenge
or git cloned to http://github.com/caius/demeters_revenge/tree/master)
.

Thanks for pointing out that article, Baz. Luke’s suggestions are an
interesting method of dealing with this. I’m going to give it a try
when I have some time.
-Nick


#6

Nick H. wrote:

@properties = @user.properties
@property = @user.properties.new
@property = @user.properties.find_by_id params[:id]

Thanks,
Nick


rspec-users mailing list
removed_email_address@domain.invalid
http://rubyforge.org/mailman/listinfo/rspec-users

I like to place this in my spec_helper.rb:

module Spec
module Mocks
module Methods
def stub_association!(association_name, methods_to_be_stubbed =
{})
mock_association = Spec::Mocks::Mock.new("#{association_name}
association")
methods_to_be_stubbed.each do |method, return_value|
mock_association.stub!(method).and_return(return_value)
end
self.stub!(association_name).and_return(mock_association)
mock_association
end
end
end
end

The you can say stuff like:

@user.stub_association!(:properties, :new => mock_model(Property),
:find_by_id => mock_model(Property))

HTH,
Ben


#7

On 2008-11-18, at 15:49, Ben M. wrote:

module Mocks
end

HTH,
Ben

That’s very similar to Matthew Heidemann’s #stub_association! . I
can’t say who wrote it first, though =) Regardless, that’s what I’ve
been using, and find it to be quite efficient.
-Nick


#8

On 2008-11-19, at 12:26, Chris F. wrote:

methods_to_be_stubbed = {})

foo.stub_association_with(:bar, [@bar1, @bar2, @bar3], :find => @bar1)

and also gives me some more fine grained control about stubbing the
association

foo.stub_association_with(:bar, [@bar1, @bar2, @bar3]) do |assn|
assn.stub!(:find).with(1).and_return @bar1
assn.stub!(:find).with(2).and_return @bar2
assn.stub!(:find).with(5).and_raise RecordNotFound
end

Sending in a block like that is a great idea.


#9

Nick H. wrote:

mocking and stubbing calls such like these?:
module Spec
end

http://rubyforge.org/mailman/listinfo/rspec-users
Probably Matt… I can’t remember who posted it first… But I got it
from the list a while ago and it has been very useful in a lot of my
apps.
-Ben