spy 1.0.1 → 1.0.3

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