[Rspec-devel] define_instance_method, stub_with, and mock_with

David Chelimsky dchelimsky at gmail.com
Mon Aug 28 00:25:24 EDT 2006


On 8/27/06, aslak hellesoy <aslak.hellesoy at gmail.com> wrote:
> On 8/28/06, David Chelimsky <dchelimsky at gmail.com> wrote:
> > On 8/27/06, aslak hellesoy <aslak.hellesoy at gmail.com> wrote:
> > > On 8/27/06, David Chelimsky <dchelimsky at gmail.com> wrote:
> > > > On 8/27/06, Brian Takita <brian.takita at gmail.com> wrote:
> > > > > Hello,
> > > > >
> > > > > At work we came up with a trio of methods that mock out methods in an
> > > > > object.
> > > > >
> > > > > define_instance_method
> > > > > stub_with
> > > > > mock_withFunny thing is around the same time, this was posted.
> > > > > http://blog.seagul.co.uk/articles/2006/06/30/very-very-lightweight-mocking-ish
> > > > >
> > > > > Here is a snippit from that article.
> > > > >
> > > > > class Object
> > > > >
> > > > >   def metaclass
> > > > >     class << self; self; end
> > > > >    end
> > > > >
> > > > >   def define_instance_method(sym, &block)
> > > > >      metaclass.__send__(:define_method, sym, &block)
> > > > >   end
> > > > >
> > > > >   def stub_instance_method(sym, &block)
> > > > >      raise "#{self} does not respond to <#{sym}> and therefore cannot be
> > > > > stubbed" unless self.respond_to?(sym)
> > > > >      define_instance_method(sym, &block)
> > > > >   end
> > > > >
> > > > >   def __log__
> > > > >     @__log__ ||= []
> > > > >   end
> > > > >
> > > > >  end
> > > > >
> > > > > So you can use this like:
> > > > > o = Object.new
> > > > >  o.define_instance_method(:foo) do
> > > > >   :bar
> > > > >  end
> > > > > o.foo # return :bar
> > > > >
> > > > > We went a little further by adding the mock_with and stub_with methods,
> > > > > which take a Hash and allow you to mock out the method names corresponding
> > > > > to the keys.
> > > > >
> > > > > For example,
> > > > > o = Object.new
> > > > > two_value = 2
> > > > >  o.stub_with(:one => 1, :two => proc {two_value})
> > > > > o.one # returns 1
> > > > > o.two # returns 2
> > > > >
> > > > >  o = Object.new
> > > > > mock_action = mock("mock_action")
> > > > >  mock_action.should_receive(:something).with(:grey_poupon)
> > > > > o.mock_with (:receive => proc {mock_action)}
> > > > > o.receive.something(:grey_poupon)
> > > > >
> > > > > David, I also remember you where working on acts_as_mock. This has helped us
> > > > > with our tests and I think it would also make using specs more convenient
> > > > > and easier to read.
> > > > > What does everybody think? If it sounds good, I'll create a patch.
> > > >
> > > > I started working on acts_as_mock because rails forces us to use
> > > > static methods to find objects, which couples us to the database in
> > > > our controller specs:
> > > >
> > > > Person.find(123)
> > > >
> > > > In retrospect, all we really need is the ability to mock class level
> > > > methods and then return a mock:
> > > >
> > > > specify "should display person when show is requested" do
> > > >   person = mock("person")
> > > >   Person.should_receive(:find).with(123).and_return(person)
> > > >   person.should_receive(:name).and_return("Joe")
> > > >   get 'show'
> > > > end
> > > >
> > > > specify "should ask Person for a new one when create is requested" do
> > > >   person = mock("person")
> > > >   Person.should_receive(:new).and_return(person)
> > > >   get 'create'
> > > > end
> > > >
> > > > The code for this much is already in a branch, so I'll revisit that in
> > > > the next few days.
> > > >
> > > > As for the idea you are presenting, in general, I think that mocking
> > > > methods on real instances is  a risky proposition. I've seen this
> > > > result in tests that are very difficult to understand when they fail,
> > > > as some methods on the instance are mocked and some are not. I also
> > > > have yet to see a case where   using partial mocks is a better
> > > > decision than improving the decoupling in the system under test.
> > > >
> > >
> > > Isn't acts_as_mock (the branch experiment) using 'partial' mocking?
> > > (Redefining class and instance methods on existing classes)
> >
> > It was originally, but I'm going to try to do it w/ only mocking the
> > class methods so we can use them to return mocks rather than returning
> > instances acting like mocks. I think that will be cleaner.
> >
>
> In Ruby, classes are *real* objects
> (http://www.rubycentral.com/book/ref_c_class.html), so making this
> available for classes should be no different than making it available
> for all objects. -Unless we make it explicitly available for Class
> only, which to me seems like a somewhat arbitrary limitation.

I appreciate that classes are objects, and I've already implemented a
partially working version of acts_as_mock that works equally well (not
that well yet, but equally so) for any class (i.e. classes and their
classes).

The question is one of principle. My experience with partial mocks is
that they are not that helpful and are generally a bandaid for testing
(yes, testing) a highly coupled, already existing design. If RSpec is
a design tool, then it should promote good design. If we agree on that
principle, then partial mocks for instances shouldn't be supported.
The only reason to support them for classes is to intercept class
level methods that are implemented using the class name directly:

MyClass.some_method

Thoughts?


More information about the Rspec-devel mailing list