Forum: RSpec Mocking and stubbing model relationships

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Nick H. (Guest)
on 2008-11-18 07:42
(Received via mailing list)
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
Matt W. (Guest)
on 2008-11-18 10:44
(Received via mailing list)
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
Rahoul B. (Guest)
on 2008-11-18 15:21
(Received via mailing list)
-----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-----
Ben M. (Guest)
on 2008-11-18 22:50
(Received via mailing list)
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
Chris F. (Guest)
on 2008-11-19 19:26
(Received via mailing list)
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
Nick H. (Guest)
on 2008-11-20 19:55
(Received via mailing list)
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
Nick H. (Guest)
on 2008-11-20 19:55
(Received via mailing list)
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.
Nick H. (Guest)
on 2008-11-20 19:55
(Received via mailing list)
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
Ben M. (Guest)
on 2008-11-20 20:00
(Received via mailing list)
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
This topic is locked and can not be replied to.