From: Gary Shea Date: 2005-08-19T09:12:08+09:00 Subject: [ANN] Mockery initial release (yet another dynamic mock object generator) Ruby is a really easy language to write mocks in, and it took thousands of lines of test-first code before I finally got tired of typing unit-test code like: x_class = Class.new(Some::Klass) x_class.class_eval { attr_reader :blah attr_writer :ret_val def doit(blah) @blah = blah return @ret_val end } x = x_class.new ret_val = Object.new x.ret_val = ret_val rv = x.please_call_doit(blah) assert_equal(blah, x.blah) assert_equal(ret_val, rv) There's probably one of these mock objects for every 5-10 lines of my program code, and that adds up to a LOT of typing. Mockery (http://rubyforge.org/projects/mockery) is a Ruby version of the kind of dynamic mock generator that is commonplace in Java. The Java versions tend to do a lousy job mocking classes as opposed to interfaces, but we don't have that problem in Ruby (not only are there no interfaces, but dynamic class modification is SO easy it's just not an issue). A first draft of the above example using Mockery looks like: ctl = Mockery::Controller.new(Some::Klass) ctl.record do |x| x.doit('blah') end ctl.try do |x| x.please_call_doit('blah') end assert_equal(true, ctl.validate, ctl.error_report) This code will detect if the call to #please_call_doit really calls #doit with the correct argument, exactly one time. #doit need not actually exist in Some::Klass -- because it's mentioned in the #record block, it will be mocked in the #try block. You may be thinking that the above test fails to handle the return value that the hand-coded example checks. Mockery can do that too. Each time a call is made to a recording object (arguments to the #record block), a call object is returned. The call object's #return_value= method may be used to set a desired return value: ctl = Mockery::Controller.new(Some::Klass) ctl.record do |x| call = x.doit(blah) call.return_value = 'blork' end ctl.try do |x| rv = x.please_call_doit(blah) assert_equal('blork', rv) end assert_equal(true, ctl.validate, ctl.error_report) which fully captures the test done in the coded-by-hand version. The lines-of-code count is not vastly different, but the potential for error is, as is the amount of thought that goes into the test. I find working with Mockery much faster. Finally, an arbitrary number of classes to mock may be handed to the Mockery::Controller#new, each of which will be provided with a recorder argument in the #record block, and a mock in the #try block. This is a first release, so it's far short of perfect. I have tentative plans for a number of improvements, and I'm open to suggestions. I'm especially open to patches :) The biggest weakness of Mockery at the moment is that it does not have a clue about mocking methods that use a block. Let me catch my breath before tackling that one ;) Regards, Gary Shea