1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
|
require "spy/exceptions"
require "spy/core_ext/marshal"
require "spy/agency"
require "spy/api"
require "spy/base"
require "spy/call_log"
require "spy/constant"
require "spy/mock"
require "spy/nest"
require "spy/subroutine"
require "spy/version"
module Spy
class << self
# create a spy on given object
# @param base_object
# @param method_names *[Hash,Symbol] will spy on these methods and also set default return values
# @return [Subroutine, Array<Subroutine>]
def on(base_object, *method_names)
spies = method_names.map do |method_name|
create_and_hook_spy(base_object, method_name)
end.flatten
spies.size > 1 ? spies : spies.first
end
# removes the spy from the from the given object
# @param base_object
# @param method_names *[Symbol]
# @return [Subroutine, Array<Subroutine>]
def off(base_object, *method_names)
removed_spies = method_names.map do |method_name|
spy = Subroutine.get(base_object, method_name)
if spy
spy.unhook
end
end
removed_spies.size > 1 ? removed_spies : removed_spies.first
end
# stubs the instance method of a given Class so all instance methods of this
# class will have the given method stubbed
# @param base_class [Class] The class you wish to stub the instance methods of
# @param method_names *[Symbol, Hash]
# @return [Spy,Array<Spy>]
def on_instance_method(base_class, *method_names)
spies = method_names.map do |method_name|
create_and_hook_spy(base_class, method_name, false)
end.flatten
spies.size > 1 ? spies : spies.first
end
# remove the stub from given Class
# @param base_class [Class]
# @param method_names *[Symbol]
# @return [Spy]
def off_instance_method(base_class, *method_names)
removed_spies = method_names.map do |method_name|
spy = Subroutine.get(base_class, method_name, false)
if spy
spy.unhook
else
raise NoSpyError, "#{method_name} was not hooked on #{base_class.inspect}."
end
end
removed_spies.size > 1 ? removed_spies : removed_spies.first
end
# create a stub for constants on given module
# @param base_module [Module]
# @param constant_names *[Symbol, Hash]
# @return [Constant, Array<Constant>]
def on_const(base_module, *constant_names)
if base_module.is_a?(Hash) || base_module.is_a?(Symbol)
constant_names.unshift(base_module)
base_module = Object
end
spies = constant_names.map do |constant_name|
case constant_name
when Symbol
Constant.on(base_module, constant_name)
when Hash
constant_name.map do |name, result|
Constant.on(base_module, name).and_return(result)
end
else
raise ArgumentError, "#{constant_name.class} is an invalid input, #on only accepts Symbol, and Hash"
end
end.flatten
spies.size > 1 ? spies : spies.first
end
# removes stubs from given module
# @param base_module [Module]
# @param constant_names *[Symbol]
# @return [Constant, Array<Constant>]
def off_const(base_module, *constant_names)
if base_module.is_a?(Symbol)
constant_names.unshift(base_module)
base_module = Object
end
spies = constant_names.map do |constant_name|
unless constant_name.is_a?(Symbol)
raise ArgumentError, "#{constant_name.class} is an invalid input, #on only accepts Symbol, and Hash"
end
Constant.off(base_module, constant_name)
end
spies.size > 1 ? spies : spies.first
end
# Create a mock object from a given class
# @param klass [Class] class you wish to mock
# @param stubs *[Symbol, Hash] methods you with to stub
# @return [Object]
def mock(klass, *stubs)
new_mock = Mock.new(klass).new
if stubs.size > 0
on(new_mock, *stubs)
end
new_mock
end
# create a mock object from a given class with all the methods stubbed out
# and returning nil unless specified otherwise.
# @param klass [Class] class you wish to mock
# @param stubs *[Symbol, Hash] methods you with to stub
# @return [Object]
def mock_all(klass, *stubs)
mock_klass = Mock.new(klass)
new_mock = mock_klass.new
spies = stubs.size > 0 ? on(new_mock, *stubs) : []
unstubbed_methods = mock_klass.mocked_methods - spies.map(&:method_name)
on(new_mock, *unstubbed_methods) if unstubbed_methods.size > 0
new_mock
end
# unhook all methods
def teardown
Agency.instance.dissolve!
end
# retrieve the spy from an object
# @param base_object
# @param method_names *[Symbol]
# @return [Subroutine, Array<Subroutine>]
def get(base_object, *method_names)
spies = method_names.map do |method_name|
Subroutine.get(base_object, method_name)
end
spies.size > 1 ? spies : spies.first
end
# retrieve the constant spies from an object
# @param base_module
# @param constant_names *[Symbol]
# @return [Constant, Array<Constant>]
def get_const(base_module, *constant_names)
if base_module.is_a?(Symbol)
constant_names.unshift(base_module)
base_module = Object
end
spies = constant_names.map do |constant_name|
Constant.get(base_module, constant_name)
end
spies.size > 1 ? spies : spies.first
end
private
def create_and_hook_spy(base_object, method_name, singleton_method = true)
case method_name
when String, Symbol
Subroutine.on(base_object, method_name, singleton_method)
when Hash
method_name.map do |name, result|
Subroutine.on(base_object, name, singleton_method).and_return(result)
end
else
raise ArgumentError, "#{method_name.class} is an invalid input, #on only accepts String, Symbol, and Hash"
end
end
end
end
|