Hi fellow Rubyists, I'd like to announce the initial release of Test::Unit::Mock, a mock object class for Test::Unit test suites. Test-Unit-Mock is a class for conveniently building mock objects in Test::Unit test cases. It is based on ideas in Ruby/Mock by Nat Pryce, but is a bit (IMHO) easier to use, and more flexible. It allows you do make a mocked object that will respond to all the methods of the real class (albeit probably not with correct results) with one line of code. Eg., mockSocket = Test::Unit::MockObject( TCPSocket ).new You can then specify return values for the methods you wish to test in one of several different ways: # Make the #addr method return three cycling values (which will be # repeated when it reaches the end 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"], ] ) # Make the #write and #read methods call a Proc and a Method, # respectively mockSocket.setReturnValues( :write => Proc::new {|str| str.length}, :read => method(:fakeRead) ) # Set up the #getsockopt method to return a value based on the # arguments given: mockSocket.setReturnValues( :getsockopt => { [Socket::SOL_TCP, Socket::TCP_NODELAY] => [0].pack("i_"), [Socket::SOL_SOCKET, Socket::SO_REUSEADDR] => [1].pack("i_"), } ) You can also set the order in which you expect methods to be called, but you don't have to do so if you don't care: mockSocket.setCallOrder( :addr, :getsockopt, :write, :read, :write ) By default, when testing for call order, other method calls may be interspersed between the calls specified without effect, and only a missing or misordered method call causes the assertions to fail. If you want the call order to be adhered to strictly, you can set that: mockSocket.strictCallOrder = true Then, when you're ready to test, just activate the object and send it off to whatever code you're testing: mockSocket.activate testedObject.setSocket( mockSocket ) ... # Check method call order on the mocked socket (adds assertions) mockSocket.verify Assertion failures contain a message that specifies exactly what went wrong, eg.: $ ruby misc/readmecode.rb 1) Failure!!! test_incorrectorder(MockTestExperiment) [./mock.rb:255]: Call order assertion failed: Expected call to :write, but got call to :read from misc/readmecode.rb:77:in `test_incorrectorder' at 0.00045 instead 2) Failure!!! test_missingcall(MockTestExperiment) [./mock.rb:255]: Call order assertion failed: Missing call to :read. If you require more advanced functionality, you can also use the mocked object class as a superclass: # Create a mock socket class class MockSocket < Test::Unit::MockObject( TCPSocket ) def initialize super setCallOrder( :read, :read, :read, :write, :read ) strictCallOrder = true @io = '' end def read( len ) super # Call the mocked method to record the call rval = @io[0,len] @io[0,len] = '' return rval end def write( str ) super # Call the mocked method to record the call @io += str return str.length end end You can also add debugging to your tests to give you a timestamped history of each call to the mock object: # Call the methods in the correct order mockSocket.addr mockSocket.getsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY ) mockSocket.write( "foo" ) mockSocket.read( 1024 ) mockSocket.write( "bar" ) mockSocket.read( 4096 ) # Check method call order on the mocked socket mockSocket.verify if $DEBUG puts "Call trace:\n\t" + mockSocket.callTrace.join("\n\t") end This outputs something like: Call trace: addr( ) at 0.00015 seconds from readmecode.rb:64:in `test' getsockopt( 6,1 ) at 0.00030 seconds from readmecode.rb:65:in `test' write( "foo" ) at 0.00040 seconds from readmecode.rb:66:in `test' read( 1024 ) at 0.00050 seconds from readmecode.rb:67:in `test' write( "bar" ) at 0.00063 seconds from readmecode.rb:68:in `test' read( 4096 ) at 0.00072 seconds from readmecode.rb:69:in `test' You can read the documentation or download it from the project page at: http://www.deveiate.org/code/Test-Unit-Mock.shtml I hope it's useful. Thanks to Nathaniel Talbott for all his work on Test::Unit, and to Nat Pryce for his on Ruby/Mock. -- Michael Granger <ged / FaerieMUD.org> Rubymage, Believer, Architect The FaerieMUD Consortium <http://www.FaerieMUD.org/>