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/