| Path: | README |
| Last Update: | Fri Apr 13 16:06:24 UTC 2007 |
FlexMock is a simple mock object for unit testing. The interface is simple, but still provides a good bit of flexibility.
| Version : | 0.3.0 |
| Documents : | onestepback.org/software/flexmock |
| Download : | Use RubyGems to download the flexmock gem from gems.rubyforge.org |
You can install FlexMock with the following command.
$ gem install flexmock
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
include FlexMock::TestCase
def test_tempurature_sampler
readings = [10, 12, 14]
sensor = flexmock("temp")
sensor.should_receive(:read_temperature).and_return { readings.shift }
sampler = TemperatureSampler.new(sensor)
assert_equal 12, sampler.average_temp
# NOTE:
# all mocks created by the +flexmock+ method will be
# automatically verified during the test teardown.
end
end
Expectation declarators are used to specify the expectations placed upon received method calls. The following declarators may be used to create the proper expectations on a FlexMock object.
For example, in the following, messages flip and flop may be received in any order (because they are in the same group), but must occur strictly after start but before end. The message any_time may be received at any time because it is not ordered.
m = FlexMock.new
m.should_receive(:any_time)
m.should_receive(:start).ordered
m.should_receive(:flip).ordered(:flip_flop_group)
m.should_receive(:flop).ordered(:flip_flop_group)
m.should_receive(:end).ordered
The values passed to the with declarator determine the criteria for matching expectations. The first expectation found that matches the arguments in a mock method call will be used to validate that mock method call.
The following rules are used for argument matching:
Examples:
with(Integer) will match f(3)
Examples:
with(/^src/) will match f("src_object")
with(/^3\./) will match f(3.1415972)
Examples:
with(3) will match f(3)
with("hello") will match f("hello")
Examples:
with(eq(Integer)) will match f(Integer)
with(eq(Integer)) will NOT match f(3)
Note: If you do not use the FlexMock::TestCase Test Unit integration module, or the FlexMock::ArgumentTypes module, you will have to fully qualify the eq method:
with(FlexMock.eq(Integer)) will match f(Integer)
with(FlexMock.eq(Integer)) will NOT match f(3)
Examples (assumes either the FlexMock::TestCase or FlexMock::ArgumentTypes mix-ins has been included):
with(any) will match f(3)
with(any) will match f("hello")
with(any) will match f(Integer)
with(any) will match f(nil)
Examples (assumes FlexMock::ArguementTypes has been included):
with(on { |arg| (arg % 2) == 0 } )
will match any even integer.
The queries my have any arguments. The update must have a specific argument of 5.
class TestDb
include FlexMock::TestCase
def test_db
db = flexmock('db')
db.should_receive(:query).and_return([1,2,3])
db.should_receive(:update).with(5).and_return(nil).once
# test code here
end
end
(This and following examples assume that the FlexMock::TestCase module is being used.)
All the query message must occur before any of the update messages.
def test_query_and_update
db = flexmock('db')
db.should_receive(:query).and_return([1,2,3]).ordered
db.should_recieve(:update).and_return(nil).ordered
# test code here
end
The queries should happen after startup but before finish. The queries themselves may happen in any order (because they are in the same order group). 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.
def test_ordered_queries
db = flexmock('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
The record mode interface offers much the same features as the should_receive interface introduced so far, but it allows the messages to be sent directly to a recording object rather than be specified indirectly using a symbol.
def test_ordered_queries_in_record_mode
db = flexmock('db')
db.should_expect do |rec|
rec.startup.once.ordered
rec.query("CPWR") { 12.3 }.once.ordered(:queries)
rec.query("MSFT") { 10.0 }.once.ordered(:queries)
rec.query("^....$/) { 3.3 }.at_least.once.ordered(:queries)
rec.finish)once.ordered
end
# test code here using +db+.
end
Record mode is nice when you have a known, good algorithm that can use a recording mock object to record the steps. Then you compare the execution of a new algorithm to behavior of the old using the recorded expectations in the mock. For this you probably want to put the recorder in strict mode so that the recorded expectations use exact matching on argument lists, and strict ordering of the method calls.
Note: This is most useful when there are no queries on the mock objects, because the query responses cannot be programmed into the recorder object.
def test_build_xml
builder = flexmock('builder')
builder.should_expect do |rec|
rec.should_be_strict
known_good_way_to_build_xml(rec) # record the messages
end
new_way_to_build_xml(builder) # compare to new way
end
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.
def test_multiple_gets
file = flexmock('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
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.
def test_an_important_message
m = flexmock('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.
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
| ruby-mock : | www.b13media.com/dev/ruby/mock.html |
| test-unit-mock : | www.deveiate.org/code/Test-Unit-Mock.shtml |
Copyright 2003, 2004, 2005, 2006 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.
| Author: | Jim Weirich <jim@weirichhouse.org> |
| Requires: | Ruby 1.8.x or later |
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.