= Flex Mock -- Making Mock Easy
FlexMock is a simple mock object for unit testing. The interface is
simple, but still provides a good bit of flexibility.
Version :: 0.1.5
= Links
Documents :: http://onestepback.org/software/flexmock
Download :: Use RubyGems to download the flexmock gem from http://gems.rubyforge.org
== Installation
You can install FlexMock with the following command.
$ gem install flexmock
== Simple Example
We have a data acquisition class (+TemperatureSampler+) that reads a
temperature sensor and returns an average of 3 readings. We don't
have a _real_ temperature to use for testing, so we mock one up with a
mock object that responds to the +read_temperature+ message.
Here's the complete example:
class TemperatureSampler
def initialize(sensor)
@sensor = sensor
end
def average_temp
total = (0...3).collect { @sensor.read_temperature }.inject { |i, s| i + s }
total / 3.0
end
end
class TestTemperatureSampler < Test::Unit::TestCase
def test_tempurature_sampler
readings = [10, 12, 14]
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
end
end
== Quick Reference
The following declarators may be used to create the proper
expectations on a FlexMock object.
* should_receive(symbol) -- Declares that a message
named symbol will be sent to the mock object. Further
refinements on this expected message (called an expectation) may be
chained to the +should_receive+ call.
* with(arglist) -- Declares that this expectation
matches messages that match the given argument list. The
=== operator is used on a argument by argument basis to
determine matching. This means that most literal values match
literally, class values match any instance of a class and regular
expression match any matching string (after a +to_s+ conversion).
* with_any_args -- Declares that this expectation matches the
message with any argument (default)
* with_no_args -- Declares that this expectation matches
messages with no arguments
* returns(value) -- Declares that the message will
return the given value (returns(nil) is the default).
* returns { code ... } -- Declares that the message will
return whatever the block calculates.
* zero_or_more_times -- Declares that the message is may be
sent zero or more times (default, equivalent to
at_least.never).
* once -- Declares that the message is only sent once.
at_least / at_most modifiers are allowed.
* twice -- Declares that the message is only sent twice.
at_least / at_most modifiers are allowed.
* never -- Declares that the message is never sent.
at_least / at_most modifiers are allowed.
* times(n) -- Declares that the message is sent
n times. at_least / at_most modifiers
are allowed.
* at_least -- Modifies the immediately following message
count declarator so that it means the message is sent at least that
number of times. E.g. at_least.once means the message is
sent at least once during the test, but may be sent more often.
Both at_least and at_most may be specified on the
same expectation.
* at_most -- Similar to at_least, but puts an upper
limit on the number of messages. Both at_least and
at_most may be specified on the same expectation.
* ordered -- Declares that the message is ordered and is
expected to be received in a certain position in a sequence of
messages. The message should arrive after and previously declared
ordered messages and prior to any following declared ordered
messages. Unordered messages are ignored when considering the
message order.
* ordered(n) -- Declares that the message is ordered
with a specific order number. Order numbers are normally supplied
sequentially starting with 1. Explicitly ordered messages must have
a sequence number greater than the prior implicit order number.
Using explicit order allows messages to be grouped so that the order
of messages in a group (sharing an order number) can be received in
any sequence, but the order between groups is still maintained. See
the explicit ordering example below.
== Examples
=== Expect multiple queries and a single update
The queries my have any arguments. The update must have a specific
argument of 5.
FlexMock('db').use |db|
db.should_receive(:query).and_return([1,2,3])
db.should_recieve(:update).with(5).and_return(nil).once
# test code here
end
=== Expect all queries before any updates
All the query message must occur before any of the update messages.
FlexMock('db').use |db|
db.should_receive(:query).and_return([1,2,3]).ordered
db.should_recieve(:update).and_return(nil).ordered
# test code here
end
=== Expect several queries with different parameters
The queries should happen after startup but before finish. The
queries themselves may happen in any order (because they have the same
order number). The first two queries should happen exactly once, but
the third query (which matches any query call with a four character
parameter) may be called multiple times (but at least once). Startup
and finish must also happen exactly once.
Also note that we use the +with+ method to match different arguement
values to figure out what value to return.
FlexMock('db').use |db|
db.should_receive(:startup).once.ordered
db.should_receive(:query).with("CPWR").and_return(12.3).
once.ordered(:queries)
db.should_receive(:query).with("MSFT").and_return(10.0).
once.ordered(:queries)
db.should_receive(:query).with(/^....$/).and_return(3.3).
at_least.once.ordered(:queries)
db.should_receive(:finish).once.ordered
# test code here
end
=== Expect multiple calls, returning a different value each time
Sometimes you need to return different values for each call to a
mocked method. This example shifts values out of a list for this
effect.
FlexMock('file').use |file|
return_values = ["line 1\n", "line 2\n"]
file.should_receive(:gets).with_no_args.and_return { return_values.shift }
# test code here
end
=== Ignore uninteresting messages
Generally you need to mock only those methods that return an
interesting value or wish to assert were sent in a particular manner.
Use the +should_ignore_missing+ method to turn on missing method
ignoring.
FlexMock('m').use |m|
m.should_recieve(:an_important_message).and_return(1).once
m.should_ignore_missing
# test code here
end
Note: The original +mock_ignore_missing+ is now an alias for
+should_ignore_missing+.
== Classic +mock_handle+ Interface
FlexMock still supports the simple +mock_handle+ interface used in the
original version of FlexMock. +mock_handle+ is equivalent to the
following:
def mock_handle(sym, expected_count=nil, &block)
self.should_receive(sym).times(expected_count).returns(&block)
end
== Other Mock Objects
ruby-mock :: http://www.b13media.com/dev/ruby/mock.html
test-unit-mock :: http://www.deveiate.org/code/Test-Unit-Mock.shtml
== License
Copyright 2003, 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
All rights reserved.
Permission is granted for use, copying, modification, distribution,
and distribution of modified versions of this work as long as the
above copyright notice is included.
= Other stuff
Author:: Jim Weirich
Requires:: Ruby 1.8.x or later
== Warranty
This software is provided "as is" and without any express or
implied warranties, including, without limitation, the implied
warranties of merchantibility and fitness for a particular
purpose.