spy 1.0.1 → 1.0.3
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.
- checksums.yaml +4 -4
- data/lib/spy/subroutine.rb +46 -37
- data/lib/spy/version.rb +1 -1
- data/test/integration/test_class_method.rb +24 -0
- data/test/spy/test_subroutine.rb +80 -0
- data/test/spy/test_unhook.rb +76 -0
- data/test/support/pen.rb +6 -0
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb02855e84ed97ae778d5983b8bba3e5839a6593416336a18240beb759f82b7f
|
4
|
+
data.tar.gz: df92498cacaab913d6d71d6d3e34778140d7367f741a1f1d750e96cbc2353a19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 72e822dfc3517b8bcc4d26d9ffc0db333acd4526673a765de0a19f4396292834bd0402a307d31d6ba226c5f9017a2813fba53738d5784dc00fc0aa0e66720869
|
7
|
+
data.tar.gz: 35b2c58f79ef8da6c462ba1a5739ddc2705df1544a2f75c14ecb9731c60b5f33337f94056dbf1803480699d6729f0410985194cbfe4d376852cf5417d352b8c2
|
data/lib/spy/subroutine.rb
CHANGED
@@ -33,6 +33,7 @@ module Spy
|
|
33
33
|
@base_object, @method_name = object, method_name
|
34
34
|
@singleton_method = singleton_method
|
35
35
|
@plan = nil
|
36
|
+
@call_through = false
|
36
37
|
reset!
|
37
38
|
end
|
38
39
|
|
@@ -58,14 +59,14 @@ module Spy
|
|
58
59
|
|
59
60
|
if singleton_method
|
60
61
|
if base_object.singleton_class.method_defined?(method_name) || base_object.singleton_class.private_method_defined?(method_name)
|
61
|
-
base_object.singleton_class.alias_method
|
62
|
+
base_object.singleton_class.send(:alias_method, method_name, method_name)
|
62
63
|
end
|
63
64
|
base_object.define_singleton_method(method_name, override_method)
|
64
65
|
else
|
65
66
|
if base_object.method_defined?(method_name) || base_object.private_method_defined?(method_name)
|
66
|
-
base_object.alias_method
|
67
|
+
base_object.send(:alias_method, method_name, method_name)
|
67
68
|
end
|
68
|
-
base_object.define_method
|
69
|
+
base_object.send(:define_method, method_name, override_method)
|
69
70
|
end
|
70
71
|
|
71
72
|
if [:public, :protected, :private].include? hook_opts[:visibility]
|
@@ -83,7 +84,7 @@ module Spy
|
|
83
84
|
raise NeverHookedError, "'#{method_name}' method has not been hooked" unless hooked?
|
84
85
|
|
85
86
|
method_owner.send(:remove_method, method_name)
|
86
|
-
if original_method
|
87
|
+
if original_method
|
87
88
|
original_method.owner.send(:define_method, method_name, original_method)
|
88
89
|
original_method.owner.send(original_method_visibility, method_name) if original_method_visibility
|
89
90
|
end
|
@@ -148,28 +149,7 @@ module Spy
|
|
148
149
|
# tells the spy to call the original method
|
149
150
|
# @return [self]
|
150
151
|
def and_call_through
|
151
|
-
|
152
|
-
@plan = Proc.new do |object, *args, &block|
|
153
|
-
if original_method
|
154
|
-
if original_method.is_a? UnboundMethod
|
155
|
-
bound_method = original_method.bind(object)
|
156
|
-
bound_method.call(*args, &block)
|
157
|
-
else
|
158
|
-
original_method.call(*args, &block)
|
159
|
-
end
|
160
|
-
else
|
161
|
-
base_object.send(:method_missing, method_name, *args, &block)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
else
|
165
|
-
@plan = Proc.new do |*args, &block|
|
166
|
-
if original_method
|
167
|
-
original_method.call(*args, &block)
|
168
|
-
else
|
169
|
-
base_object.send(:method_missing, method_name, *args, &block)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
152
|
+
@call_through = true
|
173
153
|
|
174
154
|
self
|
175
155
|
end
|
@@ -232,17 +212,13 @@ module Spy
|
|
232
212
|
def invoke(object, args, block, called_from)
|
233
213
|
check_arity!(args.size)
|
234
214
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
check_for_too_many_arguments!(@plan)
|
243
|
-
@plan.call(*args, &block)
|
244
|
-
end
|
245
|
-
end
|
215
|
+
result =
|
216
|
+
if @call_through
|
217
|
+
call_plan(build_call_through_plan(object), block, *args)
|
218
|
+
elsif @plan
|
219
|
+
check_for_too_many_arguments!(@plan)
|
220
|
+
call_plan(@plan, block, *args)
|
221
|
+
end
|
246
222
|
ensure
|
247
223
|
calls << CallLog.new(object, called_from, args, block, result)
|
248
224
|
end
|
@@ -355,6 +331,39 @@ module Spy
|
|
355
331
|
@method_owner ||= current_method.owner
|
356
332
|
end
|
357
333
|
|
334
|
+
def build_call_through_plan(object)
|
335
|
+
if original_method
|
336
|
+
if @base_object.is_a?(Class) && original_method.is_a?(UnboundMethod)
|
337
|
+
original_method.bind(object)
|
338
|
+
else
|
339
|
+
original_method
|
340
|
+
end
|
341
|
+
else
|
342
|
+
Proc.new do |*args, &block|
|
343
|
+
base_object.send(:method_missing, method_name, *args, &block)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def call_plan(plan, block, *args)
|
349
|
+
if ruby_27_last_arg_hash?(args)
|
350
|
+
*prefix, last = args
|
351
|
+
plan.call(*prefix, **last, &block)
|
352
|
+
else
|
353
|
+
plan.call(*args, &block)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Ruby 2.7 gives a deprecation warning about passing hash as last argument for a method
|
358
|
+
# with a double-splat operator (**), and Ruby 3 raises an ArgumentError exception.
|
359
|
+
# This checks if args has a hash as last element to extract it and pass it with double-splat to avoid an exception.
|
360
|
+
def ruby_27_last_arg_hash?(args)
|
361
|
+
last = args.last
|
362
|
+
last.instance_of?(Hash) &&
|
363
|
+
!last.empty? &&
|
364
|
+
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
|
365
|
+
end
|
366
|
+
|
358
367
|
class << self
|
359
368
|
|
360
369
|
# retrieve the method spy from an object or create a new one
|
data/lib/spy/version.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestClassMethod < Minitest::Test
|
4
|
+
def teardown
|
5
|
+
Spy::Agency.instance.dissolve!
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_and_return
|
9
|
+
klass = Class.new do
|
10
|
+
def self.class_method(*args, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
received_args = nil
|
14
|
+
received_block = nil
|
15
|
+
Spy.on(klass, :class_method).and_return do |*args, &block|
|
16
|
+
received_args = args
|
17
|
+
received_block = block
|
18
|
+
end
|
19
|
+
block = -> {}
|
20
|
+
klass.class_method(:a, :b, &block)
|
21
|
+
assert_equal [:a, :b], received_args
|
22
|
+
assert_equal block, received_block
|
23
|
+
end
|
24
|
+
end
|
data/test/spy/test_subroutine.rb
CHANGED
@@ -6,6 +6,10 @@ module Spy
|
|
6
6
|
Subroutine.new(base_object, method_name).hook
|
7
7
|
end
|
8
8
|
|
9
|
+
def spy_on_instance_method(base_object, method_name)
|
10
|
+
Subroutine.new(base_object, method_name, false).hook
|
11
|
+
end
|
12
|
+
|
9
13
|
def setup
|
10
14
|
@pen = Pen.new
|
11
15
|
end
|
@@ -109,6 +113,15 @@ module Spy
|
|
109
113
|
assert_empty @pen.written
|
110
114
|
end
|
111
115
|
|
116
|
+
def test_spy_and_return_can_call_a_block_with_hash
|
117
|
+
result = "hello world"
|
118
|
+
|
119
|
+
spy_on(@pen, :write_hash).and_return { |**opts| opts[:test] }
|
120
|
+
|
121
|
+
assert_equal result, @pen.write_hash(test: result)
|
122
|
+
assert_empty @pen.written
|
123
|
+
end
|
124
|
+
|
112
125
|
def test_spy_and_return_can_call_a_block_raises_when_there_is_an_arity_mismatch
|
113
126
|
write_spy = spy_on(@pen, :write)
|
114
127
|
write_spy.and_return do |*args|
|
@@ -137,6 +150,73 @@ module Spy
|
|
137
150
|
assert_equal string, result
|
138
151
|
end
|
139
152
|
|
153
|
+
def test_spy_and_call_through_returns_original_method_result
|
154
|
+
string = "hello world"
|
155
|
+
|
156
|
+
write_spy = spy_on(@pen, :write).and_call_through
|
157
|
+
another_spy = spy_on(@pen, :another).and_call_through
|
158
|
+
|
159
|
+
result = @pen.write(string)
|
160
|
+
|
161
|
+
assert_equal string, result
|
162
|
+
assert write_spy.has_been_called?
|
163
|
+
assert_equal 'another', @pen.another
|
164
|
+
assert another_spy.has_been_called?
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_spy_and_call_through_with_hash_original_method
|
168
|
+
string = 'test:hello world'
|
169
|
+
|
170
|
+
write_spy = spy_on(@pen, :write_hash).and_call_through
|
171
|
+
|
172
|
+
@pen.write_hash(test: 'hello world')
|
173
|
+
assert_equal string, @pen.written.last
|
174
|
+
assert write_spy.has_been_called?
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_spy_on_instance_and_call_through_returns_original_method_result
|
178
|
+
string = "hello world"
|
179
|
+
|
180
|
+
inst_write_spy = spy_on_instance_method(Pen, :write).and_call_through
|
181
|
+
inst_another_spy = spy_on_instance_method(Pen, :another).and_call_through
|
182
|
+
|
183
|
+
result = @pen.write(string)
|
184
|
+
|
185
|
+
assert_equal string, result
|
186
|
+
assert inst_write_spy.has_been_called?
|
187
|
+
assert_equal 'another', @pen.another
|
188
|
+
assert inst_another_spy.has_been_called?
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_spy_on_instance_and_call_through_with_hash
|
192
|
+
string = 'test:hello world'
|
193
|
+
|
194
|
+
inst_write_spy = spy_on_instance_method(Pen, :write_hash).and_call_through
|
195
|
+
|
196
|
+
@pen.write_hash(test: 'hello world')
|
197
|
+
|
198
|
+
assert_equal string, @pen.written.last
|
199
|
+
assert inst_write_spy.has_been_called?
|
200
|
+
end
|
201
|
+
|
202
|
+
def test_spy_on_instance_and_call_through_to_aryable
|
203
|
+
to_aryable = Class.new do
|
204
|
+
def hello
|
205
|
+
'hello'
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_ary
|
209
|
+
[1]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
inst_hello_spy = spy_on_instance_method(to_aryable, :hello).and_call_through
|
214
|
+
inst = to_aryable.new
|
215
|
+
|
216
|
+
assert_equal 'hello', inst.hello
|
217
|
+
assert inst_hello_spy.has_been_called?
|
218
|
+
end
|
219
|
+
|
140
220
|
def test_spy_hook_records_number_of_calls
|
141
221
|
pen_write_spy = spy_on(@pen, :write)
|
142
222
|
assert_equal 0, pen_write_spy.calls.size
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Spy
|
4
|
+
class TestUnhook < Minitest::Test
|
5
|
+
module ModuleFunctionStyle
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def hello
|
9
|
+
'hello world'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Injected
|
14
|
+
def hello
|
15
|
+
'hello world'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ModuleInjectedStyle
|
20
|
+
include Injected
|
21
|
+
end
|
22
|
+
|
23
|
+
class ModuleExtendedStyle
|
24
|
+
extend Injected
|
25
|
+
end
|
26
|
+
|
27
|
+
class SingletonMethodStyle
|
28
|
+
def self.hello
|
29
|
+
'hello world'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class InstanceMethodStyle
|
34
|
+
def hello
|
35
|
+
'hello world'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_ModuleFunctionStyle
|
40
|
+
spy = Spy.on(ModuleFunctionStyle, :hello).and_return('yo')
|
41
|
+
assert_equal ModuleFunctionStyle.hello, 'yo'
|
42
|
+
spy.unhook
|
43
|
+
assert_equal ModuleFunctionStyle.hello, 'hello world'
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_ModuleInjectedStyle
|
47
|
+
instance = ModuleInjectedStyle.new
|
48
|
+
spy = Spy.on(instance, :hello).and_return('yo')
|
49
|
+
assert_equal instance.hello, 'yo'
|
50
|
+
spy.unhook
|
51
|
+
assert_equal instance.hello, 'hello world'
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_ModuleExtendedStyle
|
55
|
+
spy = Spy.on(ModuleExtendedStyle, :hello).and_return('yo')
|
56
|
+
assert_equal ModuleExtendedStyle.hello, 'yo'
|
57
|
+
spy.unhook
|
58
|
+
assert_equal ModuleExtendedStyle.hello, 'hello world'
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_SingletonMethodStyle
|
62
|
+
spy = Spy.on(SingletonMethodStyle, :hello).and_return('yo')
|
63
|
+
assert_equal SingletonMethodStyle.hello, 'yo'
|
64
|
+
spy.unhook
|
65
|
+
assert_equal SingletonMethodStyle.hello, 'hello world'
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_InstanceMethodStyle
|
69
|
+
instance = InstanceMethodStyle.new
|
70
|
+
spy = Spy.on(instance, :hello).and_return('yo')
|
71
|
+
assert_equal instance.hello, 'yo'
|
72
|
+
spy.unhook
|
73
|
+
assert_equal instance.hello, 'hello world'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/test/support/pen.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Ong
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -157,19 +157,21 @@ files:
|
|
157
157
|
- spec/spy/to_ary_spec.rb
|
158
158
|
- spy.gemspec
|
159
159
|
- test/integration/test_api.rb
|
160
|
+
- test/integration/test_class_method.rb
|
160
161
|
- test/integration/test_constant_spying.rb
|
161
162
|
- test/integration/test_instance_method.rb
|
162
163
|
- test/integration/test_mocking.rb
|
163
164
|
- test/integration/test_subroutine_spying.rb
|
164
165
|
- test/spy/test_mock.rb
|
165
166
|
- test/spy/test_subroutine.rb
|
167
|
+
- test/spy/test_unhook.rb
|
166
168
|
- test/support/pen.rb
|
167
169
|
- test/test_helper.rb
|
168
170
|
homepage: https://github.com/ryanong/spy
|
169
171
|
licenses:
|
170
172
|
- MIT
|
171
173
|
metadata: {}
|
172
|
-
post_install_message:
|
174
|
+
post_install_message:
|
173
175
|
rdoc_options: []
|
174
176
|
require_paths:
|
175
177
|
- lib
|
@@ -184,8 +186,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
186
|
- !ruby/object:Gem::Version
|
185
187
|
version: '0'
|
186
188
|
requirements: []
|
187
|
-
rubygems_version: 3.
|
188
|
-
signing_key:
|
189
|
+
rubygems_version: 3.3.17
|
190
|
+
signing_key:
|
189
191
|
specification_version: 4
|
190
192
|
summary: A simple modern mocking library that uses the spy pattern and checks method's
|
191
193
|
existence and arity.
|
@@ -207,11 +209,13 @@ test_files:
|
|
207
209
|
- spec/spy/stub_spec.rb
|
208
210
|
- spec/spy/to_ary_spec.rb
|
209
211
|
- test/integration/test_api.rb
|
212
|
+
- test/integration/test_class_method.rb
|
210
213
|
- test/integration/test_constant_spying.rb
|
211
214
|
- test/integration/test_instance_method.rb
|
212
215
|
- test/integration/test_mocking.rb
|
213
216
|
- test/integration/test_subroutine_spying.rb
|
214
217
|
- test/spy/test_mock.rb
|
215
218
|
- test/spy/test_subroutine.rb
|
219
|
+
- test/spy/test_unhook.rb
|
216
220
|
- test/support/pen.rb
|
217
221
|
- test/test_helper.rb
|