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?
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
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
!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
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