spy 1.0.1 → 1.0.2

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: 3906b9e65a981746a6dc4e51106ed64fc925478cb73335eb8a9d46ae9fc31931
4
+ data.tar.gz: 85e19b544c6ccb98616dd846db8263da3d0e8abcfd2158a5749d8988c5dc32e8
5
5
  SHA512:
6
- metadata.gz: abb91b0bcef139e03b90cb59f2f1b5d9f0c52c8137beafab301f56a96d44dbcffc203bcf0c69cd7e5376bed68549e0bb9bf407b4f85e07fc91d875d401ce39d0
7
- data.tar.gz: 58d355373220b031ba5740b0cfdc2a6d9bb94a7c39166fafae23896342eec3c3b907994f7224e80abc7f2d1ae289048d98cd56a05750569befa62c98f6330a22
6
+ metadata.gz: 57544c0872ff818dc065037e1e2a10c34b1edb698ad84ebf1e184aa190d24c8663d51d011619bb6a1024c93f1d94a03a8bd6578274605a56f0b0ba06aabf6abc
7
+ data.tar.gz: 43b7ca82d10759bb6083fdd76b27941425cefbcc63bc2b8a585b34d3c8e2c2659f24c1f4f32843ee1c1b8f3aee1d64c127fb769774430e77a31ea7005aeebbcb
@@ -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]
@@ -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,17 @@ 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
+ if base_object.is_a? Class
221
+ call_plan(@plan, block, object, *args)
222
+ else
223
+ call_plan(@plan, block, *args)
224
+ end
225
+ end
246
226
  ensure
247
227
  calls << CallLog.new(object, called_from, args, block, result)
248
228
  end
@@ -355,6 +335,39 @@ module Spy
355
335
  @method_owner ||= current_method.owner
356
336
  end
357
337
 
338
+ def build_call_through_plan(object)
339
+ if original_method
340
+ if @base_object.is_a?(Class) && original_method.is_a?(UnboundMethod)
341
+ original_method.bind(object)
342
+ else
343
+ original_method
344
+ end
345
+ else
346
+ Proc.new do |*args, &block|
347
+ base_object.send(:method_missing, method_name, *args, &block)
348
+ end
349
+ end
350
+ end
351
+
352
+ def call_plan(plan, block, *args)
353
+ if ruby_27_last_arg_hash?(args)
354
+ *prefix, last = args
355
+ plan.call(*prefix, **last, &block)
356
+ else
357
+ plan.call(*args, &block)
358
+ end
359
+ end
360
+
361
+ # Ruby 2.7 gives a deprecation warning about passing hash as last argument for a method
362
+ # with a double-splat operator (**), and Ruby 3 raises an ArgumentError exception.
363
+ # This checks if args has a hash as last element to extract it and pass it with double-splat to avoid an exception.
364
+ def ruby_27_last_arg_hash?(args)
365
+ last = args.last
366
+ last.instance_of?(Hash) &&
367
+ !last.empty? &&
368
+ Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0")
369
+ end
370
+
358
371
  class << self
359
372
 
360
373
  # 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.2"
3
3
  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
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.2
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-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -169,7 +169,7 @@ homepage: https://github.com/ryanong/spy
169
169
  licenses:
170
170
  - MIT
171
171
  metadata: {}
172
- post_install_message:
172
+ post_install_message:
173
173
  rdoc_options: []
174
174
  require_paths:
175
175
  - lib
@@ -185,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
185
  version: '0'
186
186
  requirements: []
187
187
  rubygems_version: 3.1.2
188
- signing_key:
188
+ signing_key:
189
189
  specification_version: 4
190
190
  summary: A simple modern mocking library that uses the spy pattern and checks method's
191
191
  existence and arity.