require 'singleton' require 'not_a_mock/object_extensions' module NotAMock # The CallRecorder is a singleton that keeps track of all the call # recording hooks installed, and keeps a central record of calls. class CallRecorder include Singleton def initialize @calls = [] @tracked_methods = [] end # An array of recorded calls in chronological order. # # Each call is represented by a hash, in this format: # { :object => example_object, :method => :example_method, :args => ["example argument"], :result => "example result" } attr_reader :calls # Return an array of all the calls made to any method of +object+, in chronological order. def calls_by_object(object) @calls.select {|call| call[:object] == object } end # Return an array of all the calls made to +method+ of +object+, in chronological order. def calls_by_object_and_method(object, method) @calls.select {|call| call[:object] == object && call[:method] == method } end # Patch +object+ so that future calls to +method+ will be recorded. # # You should call Object#track_methods rather than calling this directly. def track_method(object, method) #:nodoc: unless @tracked_methods.include?([object, method]) @tracked_methods << [object, method] add_hook(object, method) end end # Remove the patch from +object+ so that future calls to +method+ # will not be recorded. # # You should call Object#track_methods rather than calling this directly. def untrack_method(object, method) #:nodoc: if @tracked_methods.delete([object, method]) remove_hook(object, method) end end # Stop recording all calls. def untrack_all @tracked_methods.each do |object, method| remove_hook(object, method) end @tracked_methods = [] end # Remove all patches so that calls are no longer recorded, and clear the call log. def reset untrack_all @calls = [] end private def add_hook(object, method) object.meta_eval do alias_method("__unlogged_#{method}", method) define_method(method) {|*args| CallRecorder.instance.send(:record_and_send, self, method, args) } end end def record_and_send(object, method, args) result = object.send("__unlogged_#{method}", *args) @calls << { :object => object, :method => method, :args => args, :result => result } result end def remove_hook(object, method) object.meta_eval do alias_method(method, "__unlogged_#{method}") remove_method("__unlogged_#{method}") end end end end