spy 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +9 -6
- data/lib/spy/subroutine.rb +43 -19
- data/lib/spy/version.rb +1 -1
- data/test/spy/test_subroutine.rb +3 -0
- metadata +1 -1
data/README.md
CHANGED
@@ -32,7 +32,7 @@ Fail faster, code faster.
|
|
32
32
|
## Why not to use this
|
33
33
|
|
34
34
|
* mocking null objects is not supported(yet)
|
35
|
-
* no argument matchers for Spy::
|
35
|
+
* no argument matchers for `Spy::Subroutine#has_been_called_with`
|
36
36
|
* cannot watch all calls to an object to check order in which they are called
|
37
37
|
* cannot transfer nested constants when stubbing a constant
|
38
38
|
* i don't think anybody uses this anyway
|
@@ -78,7 +78,8 @@ You can force the creation of a stub on method that didn't exist but it really i
|
|
78
78
|
Spy.new(book, :flamethrower).hook(force:true).and_return("burnninante")
|
79
79
|
```
|
80
80
|
|
81
|
-
You can also stub instance methods of Classes and Modules
|
81
|
+
You can also stub instance methods of Classes and Modules. This is equivalent to
|
82
|
+
rspec-mock's `Module#any_instance`
|
82
83
|
|
83
84
|
```ruby
|
84
85
|
Spy.on_instance_method(Book, :title).and_return("Cannery Row")
|
@@ -105,7 +106,7 @@ Spy.double("book", title: "Grapes of Wrath", author: "John Steinbeck")
|
|
105
106
|
|
106
107
|
### Arbitrary Handling
|
107
108
|
|
108
|
-
If you need to have a custom method based in the method inputs just send a block to
|
109
|
+
If you need to have a custom method based in the method inputs just send a block to `#and_return`
|
109
110
|
|
110
111
|
```ruby
|
111
112
|
Spy.on(book, :read_page).and_return do |page, &block|
|
@@ -142,13 +143,13 @@ Spy.get(validator, :validate)
|
|
142
143
|
```
|
143
144
|
|
144
145
|
### Calling through
|
145
|
-
If you just want to make sure if a method is called and not override the output you can just use the
|
146
|
+
If you just want to make sure if a method is called and not override the output you can just use the `#and_call_through` method
|
146
147
|
|
147
148
|
```ruby
|
148
149
|
Spy.on(book, :read_page).and_call_through
|
149
150
|
```
|
150
151
|
|
151
|
-
By if the original method never existed it will call
|
152
|
+
By if the original method never existed it will call `#method_missing` on the spied object.
|
152
153
|
|
153
154
|
### Call Logs
|
154
155
|
|
@@ -170,6 +171,8 @@ first_call.called_from #=> "file_name.rb:line_number"
|
|
170
171
|
|
171
172
|
### MiniTest
|
172
173
|
|
174
|
+
In `test_helper.rb`
|
175
|
+
|
173
176
|
```ruby
|
174
177
|
require "spy"
|
175
178
|
MiniTest::TestCase.add_teardown_hook { Spy.teardown }
|
@@ -177,7 +180,7 @@ MiniTest::TestCase.add_teardown_hook { Spy.teardown }
|
|
177
180
|
|
178
181
|
### Rspec
|
179
182
|
|
180
|
-
In
|
183
|
+
In `spec_helper.rb`
|
181
184
|
|
182
185
|
```ruby
|
183
186
|
require "rspec/autorun"
|
data/lib/spy/subroutine.rb
CHANGED
@@ -6,17 +6,23 @@ module Spy
|
|
6
6
|
# @!attribute [r] method_name
|
7
7
|
# @return [Symbol] the name of the method that is being watched
|
8
8
|
#
|
9
|
+
# @!attribute [r] singleton_method
|
10
|
+
# @return [Boolean] if the spied method is a singleton_method or not
|
11
|
+
#
|
9
12
|
# @!attribute [r] calls
|
10
13
|
# @return [Array<CallLog>] the messages that have been sent to the method
|
11
14
|
#
|
12
15
|
# @!attribute [r] original_method
|
13
16
|
# @return [Method] the original method that was hooked if it existed
|
14
17
|
#
|
18
|
+
# @!attribute [r] original_method_visibility
|
19
|
+
# @return [Method] the original visibility of the method that was hooked if it existed
|
20
|
+
#
|
15
21
|
# @!attribute [r] hook_opts
|
16
22
|
# @return [Hash] the options that were sent when it was hooked
|
17
23
|
|
18
24
|
|
19
|
-
attr_reader :base_object, :method_name, :calls, :original_method, :hook_opts
|
25
|
+
attr_reader :base_object, :method_name, :singleton_method, :calls, :original_method, :original_method_visibility, :hook_opts
|
20
26
|
|
21
27
|
# set what object and method the spy should watch
|
22
28
|
# @param object
|
@@ -34,16 +40,18 @@ module Spy
|
|
34
40
|
# @option opts [Symbol<:public, :protected, :private>] visibility overrides visibility with whatever method is given
|
35
41
|
# @return [self]
|
36
42
|
def hook(opts = {})
|
37
|
-
@hook_opts = opts
|
38
43
|
raise "#{base_object} method '#{method_name}' has already been hooked" if hooked?
|
39
44
|
|
40
|
-
hook_opts
|
45
|
+
@hook_opts = opts
|
46
|
+
@original_method_visibility = method_visibility_of(method_name)
|
41
47
|
hook_opts[:visibility] ||= original_method_visibility
|
48
|
+
hook_opts[:force] ||= base_object.is_a?(Double)
|
42
49
|
|
43
50
|
if original_method_visibility || !hook_opts[:force]
|
44
51
|
@original_method = current_method
|
45
52
|
end
|
46
53
|
|
54
|
+
define_method_with = singleton_method ? :define_singleton_method : :define_method
|
47
55
|
base_object.send(define_method_with, method_name, override_method)
|
48
56
|
|
49
57
|
if [:public, :protected, :private].include? hook_opts[:visibility]
|
@@ -61,11 +69,12 @@ module Spy
|
|
61
69
|
raise "'#{method_name}' method has not been hooked" unless hooked?
|
62
70
|
|
63
71
|
if original_method && method_owner == original_method.owner
|
64
|
-
|
65
|
-
|
72
|
+
method_owner.send(:define_method, method_name, original_method)
|
73
|
+
method_owner.send(original_method_visibility, method_name) if original_method_visibility
|
66
74
|
else
|
67
75
|
method_owner.send(:remove_method, method_name)
|
68
76
|
end
|
77
|
+
|
69
78
|
clear_method!
|
70
79
|
Agency.instance.retire(self)
|
71
80
|
self
|
@@ -74,7 +83,7 @@ module Spy
|
|
74
83
|
# is the spy hooked?
|
75
84
|
# @return [Boolean]
|
76
85
|
def hooked?
|
77
|
-
self == self.class.get(base_object, method_name,
|
86
|
+
self == self.class.get(base_object, method_name, singleton_method)
|
78
87
|
end
|
79
88
|
|
80
89
|
# @overload and_return(value)
|
@@ -82,10 +91,29 @@ module Spy
|
|
82
91
|
#
|
83
92
|
# Tells the spy to return a value when the method is called.
|
84
93
|
#
|
94
|
+
# If a block is sent it will execute the block when the method is called.
|
95
|
+
# The airty of the block will be checked against the original method when
|
96
|
+
# you first call `and_return` and when the method is called.
|
97
|
+
#
|
98
|
+
# If you want to disable the arity checking just pass `{force: true}` to the
|
99
|
+
# value
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# spy.and_return(true)
|
103
|
+
# spy.and_return { true }
|
104
|
+
# spy.and_return(force: true) { |invalid_arity| true }
|
105
|
+
#
|
85
106
|
# @return [self]
|
86
107
|
def and_return(value = nil)
|
87
|
-
|
108
|
+
@do_not_check_plan_arity = false
|
109
|
+
|
88
110
|
if block_given?
|
111
|
+
if value.is_a?(Hash) && value.has_key?(:force)
|
112
|
+
@do_not_check_plan_arity = !!value[:force]
|
113
|
+
elsif !value.nil?
|
114
|
+
raise ArgumentError.new("value and block conflict. Choose one")
|
115
|
+
end
|
116
|
+
|
89
117
|
@plan = Proc.new
|
90
118
|
check_for_too_many_arguments!(@plan)
|
91
119
|
else
|
@@ -192,6 +220,9 @@ module Spy
|
|
192
220
|
|
193
221
|
private
|
194
222
|
|
223
|
+
# this returns a lambda that calls the spy object.
|
224
|
+
# we use eval to set the spy object id as a parameter so it can be extracted
|
225
|
+
# and looked up later using `Method#parameters`
|
195
226
|
def override_method
|
196
227
|
eval <<-METHOD, binding, __FILE__, __LINE__ + 1
|
197
228
|
__method_spy__ = self
|
@@ -202,16 +233,12 @@ module Spy
|
|
202
233
|
end
|
203
234
|
|
204
235
|
def clear_method!
|
205
|
-
@hooked = false
|
236
|
+
@hooked = @do_not_check_plan_arity = false
|
206
237
|
@hook_opts = @original_method = @arity_range = @original_method_visibility = @method_owner= nil
|
207
238
|
end
|
208
239
|
|
209
|
-
def original_method_visibility
|
210
|
-
@original_method_visibility ||= method_visibility_of(method_name)
|
211
|
-
end
|
212
|
-
|
213
240
|
def method_visibility_of(method_name, all = true)
|
214
|
-
if
|
241
|
+
if singleton_method
|
215
242
|
if base_object.public_methods(all).include?(method_name)
|
216
243
|
:public
|
217
244
|
elsif base_object.protected_methods(all).include?(method_name)
|
@@ -230,10 +257,6 @@ module Spy
|
|
230
257
|
end
|
231
258
|
end
|
232
259
|
|
233
|
-
def define_method_with
|
234
|
-
@singleton_method ? :define_singleton_method : :define_method
|
235
|
-
end
|
236
|
-
|
237
260
|
def check_arity!(arity)
|
238
261
|
return unless arity_range
|
239
262
|
if arity < arity_range.min
|
@@ -241,10 +264,11 @@ module Spy
|
|
241
264
|
elsif arity > arity_range.max
|
242
265
|
raise ArgumentError.new("wrong number of arguments (#{arity} for #{arity_range.max})")
|
243
266
|
end
|
267
|
+
true
|
244
268
|
end
|
245
269
|
|
246
270
|
def check_for_too_many_arguments!(block)
|
247
|
-
return
|
271
|
+
return if @do_not_check_plan_arity || arity_range.nil?
|
248
272
|
min_arity = block.arity
|
249
273
|
min_arity = min_arity.abs - 1 if min_arity < 0
|
250
274
|
|
@@ -273,7 +297,7 @@ module Spy
|
|
273
297
|
end
|
274
298
|
|
275
299
|
def current_method
|
276
|
-
|
300
|
+
singleton_method ? base_object.method(method_name) : base_object.instance_method(method_name)
|
277
301
|
end
|
278
302
|
|
279
303
|
def method_owner
|
data/lib/spy/version.rb
CHANGED
data/test/spy/test_subroutine.rb
CHANGED