Class: Spy::Subroutine
Instance Attribute Summary collapse
-
#base_object ⇒ Object
readonly
The object that is being watched.
-
#calls ⇒ Array<CallLog>
readonly
The messages that have been sent to the method.
-
#hook_opts ⇒ Hash
readonly
The options that were sent when it was hooked.
-
#method_name ⇒ Symbol
readonly
The name of the method that is being watched.
-
#original_method ⇒ Method
readonly
The original method that was hooked if it existed.
-
#original_method_visibility ⇒ Method
readonly
The original visibility of the method that was hooked if it existed.
-
#singleton_method ⇒ Boolean
readonly
If the spied method is a singleton_method or not.
Class Method Summary collapse
-
.get(base_object, method_name, singleton_method = true) ⇒ Array<Subroutine>
retrieve the method spy from an object or return nil.
-
.get_spies(base_object, singleton_methods = true) ⇒ Array<Subroutine>
retrieve all the spies from a given object.
- .get_spy_id(method) ⇒ Object
- .off(base_object, method_name, singleton_method = true) ⇒ Object
-
.on(base_object, method_name, singleton_method = true) ⇒ Array<Subroutine>
retrieve the method spy from an object or create a new one.
Instance Method Summary collapse
-
#and_call_through ⇒ self
tells the spy to call the original method.
-
#and_raise(exception = RuntimeError, message = nil) ⇒ self
Tells the object to raise an exception when the message is received.
-
#and_return(value = nil, &block) ⇒ self
Tells the spy to return a value when the method is called.
-
#and_throw(*args) ⇒ self
Tells the object to throw a symbol (with the object if that form is used) when the message is received.
-
#and_yield(*args) {|eval_context = Object.new| ... } ⇒ self
Tells the object to yield one or more args to a block when the message is received.
-
#has_been_called? ⇒ Boolean
if the method was called it will return true.
-
#has_been_called_with?(*args, **kwargs, &block) ⇒ Boolean
check if the method was called with the exact arguments.
-
#hook(opts = {}) ⇒ self
hooks the method into the object and stashes original method if it exists.
-
#hooked? ⇒ Boolean
is the spy hooked?.
-
#initialize(object, method_name, singleton_method = true) ⇒ Subroutine
constructor
set what object and method the spy should watch.
-
#invoke(object, args, kwargs, block, called_from) ⇒ Object
invoke that the method has been called.
-
#reset! ⇒ Object
reset the call log.
-
#unhook ⇒ self
unhooks method from object.
Constructor Details
#initialize(object, method_name, singleton_method = true) ⇒ Subroutine
set what object and method the spy should watch
32 33 34 35 36 37 38 |
# File 'lib/spy/subroutine.rb', line 32 def initialize(object, method_name, singleton_method = true) @base_object, @method_name = object, method_name @singleton_method = singleton_method @plan = nil @call_through = false reset! end |
Instance Attribute Details
#base_object ⇒ Object (readonly)
Returns the object that is being watched.
|
# File 'lib/spy/subroutine.rb', line 4
|
#calls ⇒ Array<CallLog> (readonly)
Returns the messages that have been sent to the method.
|
# File 'lib/spy/subroutine.rb', line 4
|
#hook_opts ⇒ Hash (readonly)
Returns the options that were sent when it was hooked.
|
# File 'lib/spy/subroutine.rb', line 4
|
#method_name ⇒ Symbol (readonly)
Returns the name of the method that is being watched.
|
# File 'lib/spy/subroutine.rb', line 4
|
#original_method ⇒ Method (readonly)
Returns the original method that was hooked if it existed.
|
# File 'lib/spy/subroutine.rb', line 4
|
#original_method_visibility ⇒ Method (readonly)
Returns the original visibility of the method that was hooked if it existed.
|
# File 'lib/spy/subroutine.rb', line 4
|
#singleton_method ⇒ Boolean (readonly)
Returns if the spied method is a singleton_method or not.
|
# File 'lib/spy/subroutine.rb', line 4
|
Class Method Details
.get(base_object, method_name, singleton_method = true) ⇒ Array<Subroutine>
retrieve the method spy from an object or return nil
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/spy/subroutine.rb', line 377 def get(base_object, method_name, singleton_method = true) if singleton_method if base_object.respond_to?(method_name, true) spied_method = base_object.method(method_name) end elsif (base_object.public_instance_methods + base_object.protected_instance_methods + base_object.private_instance_methods).include?(method_name) spied_method = base_object.instance_method(method_name) end if spied_method Agency.instance.find(get_spy_id(spied_method)) end end |
.get_spies(base_object, singleton_methods = true) ⇒ Array<Subroutine>
retrieve all the spies from a given object
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/spy/subroutine.rb', line 397 def get_spies(base_object, singleton_methods = true) all_methods = if singleton_methods base_object.public_methods(false) + base_object.protected_methods(false) + base_object.private_methods(false) else base_object.public_instance_methods(false) + base_object.protected_instance_methods(false) + base_object.private_instance_methods(false) end all_methods.map do |method_name| Agency.instance.find(get_spy_id(base_object.method(method_name))) end.compact end |
.get_spy_id(method) ⇒ Object
415 416 417 418 419 420 421 422 |
# File 'lib/spy/subroutine.rb', line 415 def get_spy_id(method) if method.parameters[0].is_a?(Array) && method.parameters[0][1] raw_id = method.parameters[0][1].to_s if raw_id.start_with?(SPY_ARGS_PREFIX) raw_id[SPY_ARGS_PREFIX.length..-1].to_i end end end |
.off(base_object, method_name, singleton_method = true) ⇒ Object
366 367 368 369 370 |
# File 'lib/spy/subroutine.rb', line 366 def off(base_object, method_name, singleton_method = true) spy = get(base_object, method_name, singleton_method = true) raise NoSpyError, "#{method_name} was not spied on #{base_object}" unless spy spy.unhook end |
.on(base_object, method_name, singleton_method = true) ⇒ Array<Subroutine>
retrieve the method spy from an object or create a new one
362 363 364 |
# File 'lib/spy/subroutine.rb', line 362 def on(base_object, method_name, singleton_method = true) new(base_object, method_name, singleton_method).hook end |
Instance Method Details
#and_call_through ⇒ self
tells the spy to call the original method
151 152 153 154 155 |
# File 'lib/spy/subroutine.rb', line 151 def and_call_through @call_through = true self end |
#and_raise ⇒ self #and_raise(ExceptionClass) ⇒ self #and_raise(ExceptionClass, message) ⇒ self #and_raise(exception_instance) ⇒ self
When you pass an exception class, the MessageExpectation will raise
an instance of it, creating it with exception
and passing message
if specified. If the exception class initializer requires more than
one parameters, you must pass in an instance and not the class,
otherwise this method will raise an ArgumentError exception.
Tells the object to raise an exception when the message is received.
173 174 175 176 177 178 179 180 |
# File 'lib/spy/subroutine.rb', line 173 def and_raise(exception = RuntimeError, = nil) if exception.respond_to?(:exception) exception = ? exception.exception() : exception.exception end @plan = Proc.new { raise exception } self end |
#and_return(value) ⇒ self #and_return(&block) ⇒ self
Tells the spy to return a value when the method is called.
If a block is sent it will execute the block when the method is called.
The airty of the block will be checked against the original method when
you first call and_return
and when the method is called.
If you want to disable the arity checking just pass {force: true}
to the
value
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/spy/subroutine.rb', line 121 def and_return(value = nil, &block) @do_not_check_plan_arity = false if block_given? if value.is_a?(Hash) && value.has_key?(:force) @do_not_check_plan_arity = !!value[:force] elsif !value.nil? raise ArgumentError, "value and block conflict. Choose one" end @plan = block check_for_too_many_arguments!(@plan) else @plan = Proc.new { value } end self end |
#and_throw(symbol) ⇒ self #and_throw(symbol, object) ⇒ self
Tells the object to throw a symbol (with the object if that form is used) when the message is received.
189 190 191 192 |
# File 'lib/spy/subroutine.rb', line 189 def and_throw(*args) @plan = Proc.new { throw(*args) } self end |
#and_yield(*args) {|eval_context = Object.new| ... } ⇒ self
Tells the object to yield one or more args to a block when the message is received.
141 142 143 144 145 146 147 |
# File 'lib/spy/subroutine.rb', line 141 def and_yield(*args) yield eval_context = Object.new if block_given? @plan = Proc.new do |&block| eval_context.instance_exec(*args, &block) end self end |
#has_been_called? ⇒ Boolean
if the method was called it will return true
196 197 198 199 |
# File 'lib/spy/subroutine.rb', line 196 def has_been_called? raise NeverHookedError unless @was_hooked calls.size > 0 end |
#has_been_called_with?(*args, **kwargs, &block) ⇒ Boolean
check if the method was called with the exact arguments
204 205 206 207 208 |
# File 'lib/spy/subroutine.rb', line 204 def has_been_called_with?(*args, **kwargs, &block) raise NeverHookedError unless @was_hooked match = block_given? ? block : proc { |call| call.args == args && call.kwargs == kwargs } calls.any?(&match) end |
#hook(opts = {}) ⇒ self
hooks the method into the object and stashes original method if it exists
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/spy/subroutine.rb', line 45 def hook(opts = {}) raise AlreadyHookedError, "#{base_object} method '#{method_name}' has already been hooked" if self.class.get(base_object, method_name, singleton_method) @hook_opts = opts @original_method_visibility = method_visibility_of(method_name) hook_opts[:visibility] ||= original_method_visibility if original_method_visibility || !hook_opts[:force] @original_method = current_method end if original_method && original_method.owner == base_object original_method.owner.send(:remove_method, method_name) end if singleton_method if base_object.singleton_class.method_defined?(method_name) || base_object.singleton_class.private_method_defined?(method_name) base_object.singleton_class.send(:alias_method, method_name, method_name) end base_object.define_singleton_method(method_name, override_method) else if base_object.method_defined?(method_name) || base_object.private_method_defined?(method_name) base_object.send(:alias_method, method_name, method_name) end base_object.send(:define_method, method_name, override_method) end if [:public, :protected, :private].include? hook_opts[:visibility] method_owner.send(hook_opts[:visibility], method_name) end Agency.instance.recruit(self) @was_hooked = true self end |
#hooked? ⇒ Boolean
is the spy hooked?
99 100 101 |
# File 'lib/spy/subroutine.rb', line 99 def hooked? self == self.class.get(base_object, method_name, singleton_method) end |
#invoke(object, args, kwargs, block, called_from) ⇒ Object
invoke that the method has been called. You really shouldn't use this method.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/spy/subroutine.rb', line 212 def invoke(object, args, kwargs, block, called_from) arity = args.size arity += 1 if !kwargs&.empty? check_arity!(arity) result = if @call_through call_plan(build_call_through_plan(object), block, *args, **kwargs) elsif @plan check_for_too_many_arguments!(@plan) call_plan(@plan, block, *args, **kwargs) end ensure calls << CallLog.new(object, called_from, args, kwargs, block, result) end |
#reset! ⇒ Object
reset the call log
229 230 231 232 233 234 |
# File 'lib/spy/subroutine.rb', line 229 def reset! @was_hooked = false @calls = [] clear_method! true end |
#unhook ⇒ self
unhooks method from object
83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/spy/subroutine.rb', line 83 def unhook raise NeverHookedError, "'#{method_name}' method has not been hooked" unless hooked? method_owner.send(:remove_method, method_name) if original_method original_method.owner.send(:define_method, method_name, original_method) original_method.owner.send(original_method_visibility, method_name) if original_method_visibility end clear_method! Agency.instance.retire(self) self end |