Forum: Ruby Library for Mocking

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.
Thiago A. (Guest)
on 2006-03-20 15:41
(Received via mailing list)
Rubysts,

I am looking for a library for mocking objects.

I am aware of FlexMock and Test::Unit::Mock (seems like RubyMock
turned into Test::Unit::Mock, am I right?), but what I am looking for
would be something more like a hybrid of the two. Maybe I am really
misguided here, but this are my (admitedly first) impressions about
both.

FlexMock works by creating a full-blown, ready-to-use, suited-to-your
needs mock object in one method call (namely FlexMock.use). Then you
can just inject it into your tested object and use it.

Test:Unit::Mock mocks, on the other side, are created with (almost) no
idea of what will they be used for. Instead, they are born in a state
in which they still need to be told what they should expect. After you
have told them everything you want to happen to them (and how they
should respond), you switch them to replay state by calling the
#activate method. With FlexMock, there is no need to switch states,
because all the setup is done before the object is created. It is like
the mock is already born in replay state.

My main complaint about Test::Unit::Mock is about the big verbose
setup phase. With it you need to call a number of 'set' methods before
doing your stuff. In my opinion, this setup code can get pretty big,
pretty fast. For instance, let's take the example from
http://www.deveiate.org/projects/Test-Unit-Mock/wi...

Here we want to write a mock for a TCP socket. We want its 'addr'
method to return one of three values on each consecutive call, so we
write

mockSocket.setReturnValues( :addr => [
  ["AF_INET", 23, "localhost", "127.0.0.1"],
  ["AF_INET", 80, "slashdot.org", "66.35.250.150"],
  ["AF_INET", 2401, "helium.ruby-lang.org", "210.251.121.214"],
] )

After setting up the return values for the other methods, we specify
the call order with
'setCallOrder'

mockSocket.setCallOrder( :addr, :getsockopt, :write, :read,
                         :write, :read )

I like the idea of the setup phase. I just don't like the particular
way it is done in Test::Unit::Mock. I prefer the record/playback
metaphor, where you actually call the methods you want to be called on
the record phase and then the object under test is responsible for
making the expected calls on playback phase. For instance, I would
like to write something like this:

# mockSocket starts in record state

mockSocket.addr do
    return "AF_INET", 23, "localhost", "127.0.0.1"
end

mockSocket.getsockopt do
    # getsockopt return stuff here
end

# we switch it to playback state here
mockSocket.activate

Is there already anything along these lines? Am I totally lost because
the libs I just listed already do that? Any ideas?

Cheers,

Thiago A.
Jim W. (Guest)
on 2006-03-20 17:57
Thiago A. wrote:
> Rubysts,
>
> I am looking for a library for mocking objects.
[...]
> FlexMock works by creating a full-blown, ready-to-use, suited-to-your
> needs mock object in one method call (namely FlexMock.use). Then you
> can just inject it into your tested object and use it.

Actually, you still need to specify the behavior of the mock.  The "use"
module method just ensures that that mock_verify is properly called at
the end of the test.

FlexMock currently has two ways of specifying behavior:  (1) the
original "mock_handles" way and (2) the JMock-like "should_receive" way.

> [...] I prefer the record/playback
> metaphor, where you actually call the methods you want to be called on
> the record phase and then the object under test is responsible for
> making the expected calls on playback phase.

Currently, FlexMock does not support a recording mode.  But one could
easily be added:

  class FlexMock
    class Recorder
      def initialize(mock)
        @mock = mock
      end
      def method_missing(sym, *args, &block)
        @mock.should_receive(sym).and_return(&block)
      end
    end

    def should_expect
      yield Recorder.new(self)
    end
  end

This allows you to do the following:

  FlexMock.use("socket") do |mock_socket|
    mock_socket.should_expect do |recording_socket|
      recording_socket.addr { ["AF_INET", 23, "localhost", "127.0.0.1"]
}
      recording_socket.getsocketopt { DEFAULT_SOCKET_OPTS }
    end

    # Testing code goes here that uses mock_socket and
    # calls addr and getsocketopt.
  end

There are some open questions.

(1) how are arguments to the mock method validated? (exact match,
constraints, no validation?)

(2) is the order of the messages important?

The implementation provided above does no argument validation (but the
arguments are sent to the recording block for hand validation) and the
order of method calls is ignored.  However, I think automatic parameter
validation could be added without too much effort.

If there is sufficient interest in a recording interface to MockFlex, I
am willing to consider it for inclusion in the library.

--
-- Jim W.
Jim W. (Guest)
on 2006-03-20 18:56
Jim W. wrote:
[...]
> However, I think automatic parameter
> validation could be added without too much effort.

Ha!  I now know the exact amount of effort to add parameter validation
... Change the method_missing definition in Recorder to read:

      def method_missing(sym, *args, &block)
        @mock.should_receive(sym).and_return(&block).with(*args)
      end

(i.e.  add ".with(*args)" to the end of the line).

Not only do you get parameter validation, but the full range of
constraints available to the "should_receive" sytle of specification.

For example:

   FlexMock.use do |mock|
     mock.should_expect do |recorder|
       recorder.add(Integer, 4) { |i, f|
         i + f
       }.at_least.once
     end

     # Test fails if mock.add is not called at least once with
     # integer for the first argument and the number 4 for the
     # second argument.
   end

--
-- Jim W.
Thiago A. (Guest)
on 2006-03-21 14:50
(Received via mailing list)
On 3/20/06, Jim W. <removed_email_address@domain.invalid> wrote:
> Actually, you still need to specify the behavior of the mock.  The "use"
> module method just ensures that that mock_verify is properly called at
> the end of the test.

Hmm... I knew I was wrong at some point. I think I got fooled by the
docs at http://onestepback.org/software/flexmock/

Somewhere in the simple example, you can read

FlexMock.use("temp") do |sensor|
    sensor = FlexMock.new
    sensor.should_receive(:read_temperature).and_return { readings.shift
}
sampler = TemperatureSampler.new(sensor)
assert_equal 12, sampler.average_temp

Because of indentation it seems that the "should_receive" methods
(i.e. the setup phase) go inside the block that gets passed to the use
method and the stimuling calls go outside. In that sense, after the
use method returns, the mock is ready for usage. But now I can see
they are really at the same level and the setup phase really precedes
the usage phase as multiple calls and not one method call. I don't
know if I made myself clear, but that was pretty much my previous
understanding.

> (1) how are arguments to the mock method validated? (exact match,
> constraints, no validation?)

Well, you pretty much answered this one yourself on you next post :-)

> (2) is the order of the messages important?

At least in my original example it is. However, I see that we may
often need to specify that order is not important, specially when we
use the repetition modifiers (like once and twice). I definitely need
some experimenting in order to clear my mind on this topic. How does
JMock handles that?

> If there is sufficient interest in a recording interface to MockFlex, I
> am willing to consider it for inclusion in the library.

You can find at least one person. Anyway, I know I am patching my
MockFlex (or is it called FlexMock?).

Cheers,

Thiago A.
Jim W. (Guest)
on 2006-03-21 16:52
Thiago A. wrote:
> On 3/20/06, Jim W. <removed_email_address@domain.invalid> wrote:
>> Actually, you still need to specify the behavior of the mock.  The "use"
>> module method just ensures that that mock_verify is properly called at
>> the end of the test.
>
> Hmm... I knew I was wrong at some point. I think I got fooled by the
> docs at http://onestepback.org/software/flexmock/
>
> Somewhere in the simple example, you can read
>
> FlexMock.use("temp") do |sensor|
>     sensor = FlexMock.new
>     sensor.should_receive(:read_temperature).and_return { readings.shift
> }
> sampler = TemperatureSampler.new(sensor)
> assert_equal 12, sampler.average_temp
>
> Because of indentation it seems that the "should_receive" methods
> (i.e. the setup phase) go inside the block that gets passed to the use
> method and the stimuling calls go outside.

Gah!  That example is all messed up.  It looks like I might have been
interupted while editting it and never got back to cleaning it up.  I
will fix this ASAP.

Sorry for the confusion.  Its not a good thing when your first example
is garbled.

> At least in my original example it is. However, I see that we may
> often need to specify that order is not important, specially when we
> use the repetition modifiers (like once and twice). [...]

Ordering issues was one of the driving forces that lead to the creation
of FlexMock.  At the time, the mocking framework I was using was too
inflexible on the allowed order of received messages.  Requiring the
exact order was, in my mind, an overspecification of the behavior I
wished to implement.  I'm pretty happy with the ordering specification
in FlexMock at the moment.

One of the advantages of a recording mode for mocks is that you can give
the recorder to a known good algorithm and record the actual calls as
expectations.    Then give the mock to a reimplemenation of the known
good algorithm and can check that the new algorithm exactly matches the
old.  Obviously, this only works for imperative interfaces (ie. no
queries, because the recorder wont return the proper values).  But for
that limited range of use, it is pretty handy.

--
-- Jim W.
This topic is locked and can not be replied to.