spy 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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