= 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.