stub_chain

136 views
Skip to first unread message

Dan Croak

unread,
Aug 6, 2009, 3:31:27 PM8/6/09
to mocha-developer
We've got a version of this in test/test_helper.rb of our Rails app:

http://blog.leshill.org/blog/2009/08/05/update-for-stub-chain-for-mocha.html

Any chance of pulling it into Mocha proper?

James Mead

unread,
Aug 7, 2009, 10:40:33 AM8/7/09
to mocha-d...@googlegroups.com
2009/8/6 Dan Croak <dcr...@thoughtbot.com>


We've got a version of this in test/test_helper.rb of our Rails app:

http://blog.leshill.org/blog/2009/08/05/update-for-stub-chain-for-mocha.html

Any chance of pulling it into Mocha proper?

Hi Dan,

Thanks for the link. I'm a bit busy with other stuff at the moment, but here are a few quick thoughts. I'd welcome feedback from you or anyone else.

There's been some discussion about this kind of feature on the mailing list in the past [1]. That email thread includes some of my thoughts on the matter [2].

As I say in that email thread, I'm not completely against including something along these lines into Mocha, but I think I'd prefer it if the user had their attention drawn to the potential issues. This could be achieved in a number of ways e.g. (a) having to require an extra file within Mocha; (b) a configuration option that has to be explicitly switched on; or (c) generating a warning by default which can be explicitly suppressed.

I'm also not 100% convinced about the syntax. Would it not be logical to also have an Object#expect_chain as well as Object#stub_chain? And should it not also work on traditional mock objects as well as non-mock objects i.e. Mock#stub_chain as well as Object#stub_chain? This starts feeling as if it is polluting the Mocha API with a lot of methods for what is really a bit of an edge case. I wonder if there is a neater way of doing it...? This is totally off the top of my head and I haven't thought it through at all, but what about :-

 stubs(method_chain(:votes, :supporting, :count)).returns(supporting_count)

Cheers, James.

Dan Croak

unread,
Aug 7, 2009, 11:15:42 AM8/7/09
to mocha-d...@googlegroups.com
James,

On Fri, Aug 7, 2009 at 10:40 AM, James Mead<james...@gmail.com> wrote:

> 2009/8/6 Dan Croak <dcr...@thoughtbot.com>

>> We've got a version of this in test/test_helper.rb of our Rails app:
>> http://blog.leshill.org/blog/2009/08/05/update-for-stub-chain-for-mocha.html

I meant to say: we have a version of stub_chain in ALL of our Rails
apps. Important distinction.

> [1]. That email thread includes some of my thoughts on the matter

Just read through them. Thanks for the links. I searched "stub_chain"
before posting but didn't find anything. Not the right keywords, I
guess.

> [2]. As I say in that email thread, I'm not completely against including
> something along these lines into Mocha, but I think I'd prefer it if the
> user had their attention drawn to the potential issues. This could be
> achieved in a number of ways e.g. (a) having to require an extra file within
> Mocha; (b) a configuration option that has to be explicitly switched on; or
> (c) generating a warning by default which can be explicitly suppressed.

Is this out-of-the-ordinary enough to require a switch or extra
configuration? I say no.

> I'm also not 100% convinced about the syntax. Would it not be logical to
> also have an Object#expect_chain as well as Object#stub_chain?

I don't think we've ever needed expect_chain. This is coming strictly
from real-world usage.

> And should it not also work on traditional mock objects as well as non-mock objects i.e.
> Mock#stub_chain as well as Object#stub_chain? This starts feeling as if it
> is polluting the Mocha API with a lot of methods for what is really a bit of
> an edge case.

I'm ambivalent about the syntax and would defer to you guys on what is
most Mocha-like. I disagree that this is an edge case.

> I wonder if there is a neater way of doing it...? This is
> totally off the top of my head and I haven't thought it through at all, but
> what about :-
>  stubs(method_chain(:votes, :supporting, :count)).returns(supporting_count)

Looks fine to me!

--
Dan Croak
@Croaky

Dan Croak

unread,
Aug 7, 2009, 11:23:01 AM8/7/09
to mocha-d...@googlegroups.com
Here's some alternatives to testing named_scopes in a Rails app. One
of the options is stub_chain. I'd love to hear people's opinions on
each style.

These are slides from a presentation I gave last week in Boston.

!SLIDE

@@@ ruby
Factory.define :recurring_event, :parent => :event do |factory|
factory.recurring true
end

Factory.define :special_event, :parent => :event do |factory|
factory.recurring false
end
@@@

!SLIDE

@@@ ruby
class Event < ActiveRecord::Base
named_scope :recurring, :conditions => { :recurring => true }
named_scope :special, :conditions => { :recurring => false }

def self.next_five_special
self.next(5).special
end
end
@@@

!SLIDE

## version 1: test spy with stub

@@@ ruby
context "next five special" do
setup do
@next_scope = stub('next_scope')
@special_scope = stub('special_scope')
@next_scope.stubs(:special).returns(@special_scope)

Event.stubs(:next).returns(@next_scope)
Event.next_five_special
end

should "get next 5 events" do
assert_received(Event, :next) { |expect| expect.with(5) }
end

should "get special events" do
assert_received(@next_scope, :special)
end
end
@@@

!SLIDE

## bad form?

* mocking the same object
* not across any boundary
* probably done to make tests faster

!SLIDE

## version 2: statist approach

@@@ ruby
context "next five special" do
setup do
@five_special = []
5.times { @five_special << Factory(:special_event) }

@sixth_special = Factory(:special_event, :start_at => 2.months.ago)
@recurring = Factory(:recurring_event)

@special_events = Event.next_five_special
end

should "get next 5 events" do
assert_equal @five_special, @special_events
end

should "not get recurring event" do
assert ! @special_events.include(@recurring)
end
end
@@@

!SLIDE

## bad form?

* creates many records in the database
* slower

!SLIDE

## stub_chain

@@@ ruby
module StubChainMocha
module Object
def stub_chain(*methods)
if methods.length > 1
next_in_chain = ::Object.new
stubs(methods.shift).returns(next_in_chain)
next_in_chain.stub_chain(*methods)
else
stubs(methods.shift)
end
end
end
end

Object.send(:include, StubChainMocha::Object)
@@@

!SLIDE

## version 3: stub_chain with test spies

@@@ ruby
context "next five specials" do
setup do
@event = Factory(:special_event)
Event.stub_chain(:next, :special).returns([@event])
Event.next_five_special
end

should "find next five special events" do
assert_received(Event, :next) { |expect| expect.with(5) }
assert_received(Event, :special)
end
end
@@@

!SLIDE

## bad form?

* short and readable but does it test everything?

Dan Croak
@Croaky

Dan Croak

unread,
Aug 7, 2009, 11:25:53 AM8/7/09
to mocha-d...@googlegroups.com
Hmm, that Event model doesn't show the next named_scope. Updated:

!SLIDE

@@@ ruby
class Event < ActiveRecord::Base

named_scope :special, :conditions => { :recurring => false }

named_scope :next, lambda {|*args|
limit = args.first || 1
{ :conditions => ['date > ?', DateTime.now],
:order => 'date asc',
:limit => limit }
}

def self.next_five_special
self.next(5).special
end
end
@@@

--
Dan Croak
@Croaky

Dan Croak

unread,
Aug 7, 2009, 11:27:35 AM8/7/09
to mocha-d...@googlegroups.com
Outside of Rails, I haven't found myself wanting stub_chain as often,
but it is certainly a possibility.

If the worry is abuse (drawing developer to potential issues), I'd
suggest including a "teaching moment" in the docs for the method.
Something about Law of Demeter, for instance.

--
Dan Croak
@Croaky

Josh Clayton

unread,
Aug 7, 2009, 12:05:10 PM8/7/09
to mocha-developer
Dan,

I agree about using this outside of Rails in that I never do it.
Honestly, the only times I can think of needing this is for testing
chained named scopes. That being said, I still think there are other
cases where this is useful.

+1
Reply all
Reply to author
Forward
0 new messages