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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa89505674a7286f055ee019988cf29debbdd5cb7e525a022864f551d20ae842
4
- data.tar.gz: fa61fa5e78f5aea6fab75bcc471bdcc8032182d8e9c7528e9370b91022ef7ae0
3
+ metadata.gz: eb02855e84ed97ae778d5983b8bba3e5839a6593416336a18240beb759f82b7f
4
+ data.tar.gz: df92498cacaab913d6d71d6d3e34778140d7367f741a1f1d750e96cbc2353a19
5
5
  SHA512:
6
- metadata.gz: abb91b0bcef139e03b90cb59f2f1b5d9f0c52c8137beafab301f56a96d44dbcffc203bcf0c69cd7e5376bed68549e0bb9bf407b4f85e07fc91d875d401ce39d0
7
- data.tar.gz: 58d355373220b031ba5740b0cfdc2a6d9bb94a7c39166fafae23896342eec3c3b907994f7224e80abc7f2d1ae289048d98cd56a05750569befa62c98f6330a22
6
+ metadata.gz: 72e822dfc3517b8bcc4d26d9ffc0db333acd4526673a765de0a19f4396292834bd0402a307d31d6ba226c5f9017a2813fba53738d5784dc00fc0aa0e66720869
7
+ data.tar.gz: 35b2c58f79ef8da6c462ba1a5739ddc2705df1544a2f75c14ecb9731c60b5f33337f94056dbf1803480699d6729f0410985194cbfe4d376852cf5417d352b8c2
@@ -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(method_name, method_name)
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(method_name, method_name)
67
+ base_object.send(:alias_method, method_name, method_name)
67
68
  end
68
- base_object.define_method(method_name, override_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 && method_owner == original_method.owner
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
- if @base_object.is_a? Class
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
- if base_object.is_a? Class
236
- result = if @plan
237
- check_for_too_many_arguments!(@plan)
238
- @plan.call(object, *args, &block)
239
- end
240
- else
241
- result = if @plan
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
@@ -1,3 +1,3 @@
1
1
  module Spy
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.3"
3
3
  end
@@ -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
@@ -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
@@ -27,6 +27,12 @@ class Pen
27
27
  end
28
28
  end
29
29
 
30
+ def write_hash(**params)
31
+ params.each do |p|
32
+ write(p.join(':'))
33
+ end
34
+ end
35
+
30
36
  def greet(hello = "hello", name)
31
37
  write("#{hello} #{name}")
32
38
  end
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.1
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: 2020-08-20 00:00:00.000000000 Z
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.1.2
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