This post originated from an RSS feed registered with Ruby Buzz
by Jay Fields.
Original Post: Ruby: Recording Method Calls and Playback With Inject
Feed Title: Jay Fields Thoughts
Feed URL: http://feeds.feedburner.com/jayfields/mjKQ
Feed Description: Blog about Ruby, Agile, Testing and other topics related to software development.
Sometimes you want to call methods on an object, but you want to delay the actual execution of those methods till a later time.
For example, in expectations you create a mock at parse time, but you actually want the mock to be available at execution time.
classSystemProcess defstart puts "started" newStartedProcess end end
Expectationsdo expect SystemProcess.new.to.receive(:start)do |process| process.start end end
In the above code you define what you expect when the file is parsed, but you actually want the process expectation to be set when the do block is executed. This can be (and is) achieved by using a recorder to record all the method calls on the process object. At execution time the method calls are played back and the initialized process object is yielded to the block.
The code for a recorder is actually quite trivial in Ruby.
classRecorder attr_reader:subject definitialize(subject) @subject= subject end
defreplay method_stack.inject(subject){|result,element| result.send element.first,*element.last } end
defmethod_stack @method_stack||=[] end
defmethod_missing(sym,*args) method_stack <<[sym, args] self end end
Here's an example usage of a recorder.
classSystemProcess defstart(in_seconds) puts "starting in #{in_seconds}" sleep in_seconds StartedProcess.new end end
classStartedProcess defpause(in_seconds) puts "pausing in #{in_seconds}" sleep in_seconds PausedProcess.new end end
classPausedProcess defstop(in_seconds) puts "stopping in #{in_seconds}" sleep in_seconds self end end
Recorder.new(SystemProcess.new).start(1).pause(2).stop(3).replay # >> starting in 1 # >> pausing in 2 # >> stopping in 3
The only thing worth noting is that by using inject you can use a method chain that returns different objects. Traditional versions of a recorder that I've seen often assume that all the methods should be called on the subject. I prefer the version that allows for object creation within the fluent interface. In practice, that's exactly what was needed for recording and playing back Mocha's expectation setting methods.