much-stub 0.1.2 → 0.1.7

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: 948b3a6fa21e50a2a221b167fe768a5d65c7a61c135622fe6ced882a5e406b03
4
- data.tar.gz: 700d700fab285da4620cb4fbd3b521a58a9ffeaf8d50afdcc96780810091e301
3
+ metadata.gz: 15204360d9518246e4abaa703670f75489b26364a262f61397956bcd6292bb91
4
+ data.tar.gz: e53db7a648e52be63ea31d6dc930d7c009e65604d05a4e1e447edcfb3ef7b296
5
5
  SHA512:
6
- metadata.gz: fecb7b76d76bc9ff338461c7ec6a48ac3bf1833bd77f4f248c9d4a145fa0b0177b3ce9d99d01add632266d7f9c5731441f0ad553f14775e7cf704aaffc80099a
7
- data.tar.gz: 55308114a2f7aac2294ba44aac2adf84152cc2213a40a9ff0118b3fe2e8d43738cbc617fb19855c63515e915bc46754fc915d86b568d7a9c97356fb619420156
6
+ metadata.gz: 5892e22dc64f46400a115d42fb7b7da3d5b82387fda5b8c2ec5f807d6a9b47e4dca5a92d86a3a97898d5f6e85885808065d591e53aba7d78303afc1396fce4a4
7
+ data.tar.gz: 4476a777e7ee869f59080f5c0bc40d2d8736d77d01588f193ea6e826f424d3abf110ca49a60437267e7e16d5578c2f6f74bb24ce810940af82154ce0e08e7199
data/.l.yml ADDED
@@ -0,0 +1,8 @@
1
+ # https://github.com/redding/l.rb
2
+
3
+ linters:
4
+ - name: "Rubocop"
5
+ cmd: "bundle exec rubocop"
6
+ extensions:
7
+ - ".rb"
8
+ cli_abbrev: "u"
@@ -0,0 +1,3 @@
1
+ inherit_gem:
2
+ much-style-guide:
3
+ - "lib/much-style-guide/rubocop.yml"
@@ -0,0 +1 @@
1
+ 2.5.8
data/.t.yml ADDED
@@ -0,0 +1,6 @@
1
+ # https://github.com/redding/t.rb
2
+
3
+ default_cmd: bundle exec assert
4
+ test_dir: test
5
+ test_file_suffixes:
6
+ - "_tests.rb"
data/Gemfile CHANGED
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
5
+ ruby "~> 2.5"
6
+
3
7
  gemspec
4
8
 
5
- gem "pry", "~> 0.12.2"
9
+ gem "pry"
data/README.md CHANGED
@@ -110,24 +110,49 @@ my_object = my_class.new
110
110
 
111
111
  basic_method_called_with = nil
112
112
  MuchStub.(my_object, :basic_method) { |*args|
113
- basic_method_called_with = args
113
+ basic_method_called_with = MuchStub::Call.new(*args)
114
+ }
115
+ # OR
116
+ MuchStub.(my_object, :basic_method).on_call { |call|
117
+ basic_method_called_with = call
118
+ }
119
+ # OR
120
+ MuchStub.on_call(my_object, :basic_method) { |call|
121
+ # MucStub.on_call(...) { ... } is equivalent to
122
+ # MuchStub.(...).on_call { ... }
123
+ basic_method_called_with = call
114
124
  }
115
125
 
116
126
  my_object.basic_method(123)
117
- basic_method_called_with
127
+ basic_method_called_with.args
118
128
  # => [123]
119
129
 
120
- iterator_method_call_args = nil
121
- iterator_method_call_block = nil
130
+ basic_method_called_with = nil
131
+ MuchStub.(my_object, :basic_method).with(4, 5, 6) { |*args|
132
+ basic_method_called_with = MuchStub::Call.new(*args)
133
+ }
134
+ # OR
135
+ MuchStub.(my_object, :basic_method).with(4, 5, 6).on_call { |call|
136
+ basic_method_called_with = call
137
+ }
138
+
139
+ my_object.basic_method(4, 5, 6)
140
+ basic_method_called_with.args
141
+ # => [4,5,6]
142
+
143
+ iterator_method_called_with = nil
122
144
  MuchStub.(my_object, :iterator_method) { |*args, &block|
123
- iterator_method_call_args = args
124
- iterator_method_call_block = block
145
+ iterator_method_called_with = MuchStub::Call.new(*args)
146
+ }
147
+ # OR
148
+ MuchStub.(my_object, :iterator_method).on_call { |call|
149
+ iterator_method_called_with = call
125
150
  }
126
151
 
127
152
  my_object.iterator_method([1, 2, 3], &:to_s)
128
- iterator_method_call_args
153
+ iterator_method_called_with.args
129
154
  # => [[1, 2, 3]]
130
- iterator_method_call_block
155
+ iterator_method_called_with.block
131
156
  # => #<Proc:0x00007fb083a6feb0(&:to_s)>
132
157
 
133
158
  # Count method calls for spying.
@@ -145,13 +170,17 @@ basic_method_call_count
145
170
 
146
171
  basic_method_calls = []
147
172
  MuchStub.(my_object, :basic_method) { |*args|
148
- basic_method_calls << args
173
+ basic_method_calls << MuchStub::Call.new(*args)
174
+ }
175
+ # OR
176
+ MuchStub.(my_object, :basic_method).on_call { |call|
177
+ basic_method_calls << call
149
178
  }
150
179
 
151
180
  my_object.basic_method(123)
152
181
  basic_method_calls.size
153
182
  # => 1
154
- basic_method_calls.first
183
+ basic_method_calls.first.args
155
184
  # => [123]
156
185
  ```
157
186
 
@@ -218,12 +247,16 @@ basic_method_called_with
218
247
 
219
248
  basic_method_called_with = nil
220
249
  MuchStub.tap(my_object, :basic_method) { |value, *args|
221
- basic_method_called_with = args
250
+ basic_method_called_with = MuchStub::Call.new(*args)
251
+ }
252
+ # OR
253
+ MuchStub.tap_on_call(my_object, :basic_method) { |value, call|
254
+ basic_method_called_with = call
222
255
  }
223
256
 
224
257
  my_object.basic_method(123)
225
258
  # => "123"
226
- basic_method_called_with
259
+ basic_method_called_with.args
227
260
  # => [123]
228
261
  ```
229
262
 
@@ -266,6 +299,39 @@ thing.value
266
299
  # => 456
267
300
  ```
268
301
 
302
+ ### `MuchStub.spy`
303
+
304
+ Use the `.spy` method to spy on method calls. This is especially helpful for spying on _chained_ method calls.
305
+
306
+ ```ruby
307
+ # Given this object/API
308
+
309
+ myclass = Class.new do
310
+ def one; self; end
311
+ def two(val); self; end
312
+ def three; self; end
313
+ def ready?; false; end
314
+ end
315
+ myobj = myclass.new
316
+
317
+ spy =
318
+ MuchStub.spy(myobj :one, :two, :three, ready?: true)
319
+
320
+ assert_equal spy, myobj.one
321
+ assert_equal spy, myobj.two("a")
322
+ assert_equal spy, myobj.three
323
+
324
+ assert_true myobj.one.two("b").three.ready?
325
+
326
+ assert_kind_of MuchStub::CallSpy, spy
327
+ assert_equal 2, spy.one_call_count
328
+ assert_equal 2, spy.two_call_count
329
+ assert_equal 2, spy.three_call_count
330
+ assert_equal 1, spy.ready_predicate_call_count
331
+ assert_equal ["b"], spy.two_last_called_with.args
332
+ assert_true spy.ready_predicate_called?
333
+ ```
334
+
269
335
  ## Installation
270
336
 
271
337
  Add this line to your application's Gemfile:
@@ -1,5 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "much-stub/version"
2
4
 
5
+ require "much-stub/call"
6
+ require "much-stub/call_spy"
7
+
3
8
  module MuchStub
4
9
  def self.stubs
5
10
  @stubs ||= {}
@@ -9,39 +14,72 @@ module MuchStub
9
14
  MuchStub::Stub.key(obj, meth)
10
15
  end
11
16
 
12
- def self.call(*args, &block)
13
- self.stub(*args, &block)
17
+ def self.arity_matches?(method, args)
18
+ # mandatory args
19
+ return true if method.arity == args.size
20
+ # variable args
21
+ return true if method.arity < 0 && args.size >= (method.arity + 1).abs
22
+
23
+ false
14
24
  end
15
25
 
16
26
  def self.stub(obj, meth, &block)
17
- key = self.stub_key(obj, meth)
18
- self.stubs[key] ||= MuchStub::Stub.new(obj, meth, caller_locations)
19
- self.stubs[key].tap{ |s| s.do = block }
27
+ key = stub_key(obj, meth)
28
+ stubs[key] ||= MuchStub::Stub.new(obj, meth, caller_locations)
29
+ stubs[key].tap{ |s| s.do = block }
30
+ end
31
+
32
+ def self.call(*args, &block)
33
+ stub(*args, &block)
34
+ end
35
+
36
+ def self.stub_on_call(*args, &on_call_block)
37
+ stub(*args).on_call(&on_call_block)
38
+ end
39
+
40
+ def self.on_call(*args, &on_call_block)
41
+ stub_on_call(*args, &on_call_block)
20
42
  end
21
43
 
22
44
  def self.unstub(obj, meth)
23
- key = self.stub_key(obj, meth)
24
- (self.stubs.delete(key) || MuchStub::NullStub.new).teardown
45
+ key = stub_key(obj, meth)
46
+ (stubs.delete(key) || MuchStub::NullStub.new).teardown
25
47
  end
26
48
 
27
49
  def self.unstub!
28
- self.stubs.keys.each{ |key| self.stubs.delete(key).teardown }
50
+ stubs.keys.each{ |key| stubs.delete(key).teardown }
29
51
  end
30
52
 
31
53
  def self.stub_send(obj, meth, *args, &block)
32
54
  orig_caller = caller_locations
33
- stub = self.stubs.fetch(MuchStub::Stub.key(obj, meth)) do
55
+ stub = stubs.fetch(MuchStub::Stub.key(obj, meth)) do
34
56
  raise NotStubbedError, "`#{meth}` not stubbed.", orig_caller.map(&:to_s)
35
57
  end
36
58
  stub.call_method(args, &block)
37
59
  end
38
60
 
39
61
  def self.tap(obj, meth, &tap_block)
40
- self.stub(obj, meth) { |*args, &block|
41
- self.stub_send(obj, meth, *args, &block).tap { |value|
42
- tap_block.call(value, *args, &block) if tap_block
43
- }
44
- }
62
+ stub(obj, meth) do |*args, &block|
63
+ stub_send(obj, meth, *args, &block).tap do |value|
64
+ tap_block&.call(value, *args, &block)
65
+ end
66
+ end
67
+ end
68
+
69
+ def self.tap_on_call(obj, meth, &on_call_block)
70
+ tap(obj, meth) do |value, *args, &block|
71
+ on_call_block&.call(value, MuchStub::Call.new(*args, &block))
72
+ end
73
+ end
74
+
75
+ def self.spy(obj, *meths, **return_values)
76
+ MuchStub::CallSpy.new(**return_values).call_spy_tap do |spy|
77
+ meths.each do |meth|
78
+ stub(obj, meth) do |*args, &block|
79
+ spy.__send__(meth, *args, &block)
80
+ end
81
+ end
82
+ end
45
83
  end
46
84
 
47
85
  class Stub
@@ -75,11 +113,15 @@ module MuchStub
75
113
 
76
114
  def call(args, orig_caller = nil, &block)
77
115
  orig_caller ||= caller_locations
78
- unless arity_matches?(args)
79
- msg = "arity mismatch on `#{@method_name}`: " \
80
- "expected #{number_of_args(@method.arity)}, " \
81
- "called with #{args.size}"
82
- raise StubArityError, msg, orig_caller.map(&:to_s)
116
+ unless MuchStub.arity_matches?(@method, args)
117
+ raise(
118
+ StubArityError.new(
119
+ @method,
120
+ args,
121
+ method_name: @method_name,
122
+ backtrace: orig_caller,
123
+ ),
124
+ )
83
125
  end
84
126
  lookup(args, orig_caller).call(*args, &block)
85
127
  rescue NotStubbedError
@@ -89,13 +131,31 @@ module MuchStub
89
131
 
90
132
  def with(*args, &block)
91
133
  orig_caller = caller_locations
92
- unless arity_matches?(args)
93
- msg = "arity mismatch on `#{@method_name}`: " \
94
- "expected #{number_of_args(@method.arity)}, " \
95
- "stubbed with #{args.size}"
96
- raise StubArityError, msg, orig_caller.map(&:to_s)
134
+ unless MuchStub.arity_matches?(@method, args)
135
+ raise(
136
+ StubArityError.new(
137
+ @method,
138
+ args,
139
+ method_name: @method_name,
140
+ backtrace: orig_caller,
141
+ ),
142
+ )
97
143
  end
98
144
  @lookup[args] = block
145
+ self
146
+ end
147
+
148
+ def on_call(&on_call_block)
149
+ stub_block =
150
+ ->(*args, &block){
151
+ on_call_block&.call(MuchStub::Call.new(*args, &block))
152
+ }
153
+ if @lookup.empty?
154
+ @do = stub_block
155
+ elsif @lookup.value?(nil)
156
+ @lookup.transform_values!{ |value| value.nil? ? stub_block : value }
157
+ end
158
+ self
99
159
  end
100
160
 
101
161
  def teardown
@@ -106,7 +166,7 @@ module MuchStub
106
166
  end
107
167
 
108
168
  def inspect
109
- "#<#{self.class}:#{"0x0%x" % (object_id << 1)}" \
169
+ "#<#{self.class}:#{format("0x0%x", (object_id << 1))}" \
110
170
  " @method_name=#{@method_name.inspect}" \
111
171
  ">"
112
172
  end
@@ -118,7 +178,7 @@ module MuchStub
118
178
  msg = "#{object.inspect} does not respond to `#{@method_name}`"
119
179
  raise StubError, msg, orig_caller.map(&:to_s)
120
180
  end
121
- is_constant = object.kind_of?(Module)
181
+ is_constant = object.is_a?(Module)
122
182
  local_object_methods = object.methods(false).map(&:to_s)
123
183
  all_object_methods = object.methods.map(&:to_s)
124
184
  if (is_constant && !local_object_methods.include?(@method_name)) ||
@@ -129,7 +189,8 @@ module MuchStub
129
189
  method
130
190
  end
131
191
 
132
- if !local_object_methods.include?(@name) # already stubbed
192
+ # already stubbed
193
+ unless local_object_methods.include?(@name)
133
194
  @metaclass.send(:alias_method, @name, @method_name)
134
195
  end
135
196
  @method = object.method(@name)
@@ -144,20 +205,20 @@ module MuchStub
144
205
 
145
206
  def lookup(args, orig_caller)
146
207
  @lookup.fetch(args) do
147
- self.do || begin
208
+ self.do ||
209
+ begin
148
210
  msg = "#{inspect_call(args)} not stubbed."
149
211
  inspect_lookup_stubs.tap do |stubs|
150
- msg += "\nStubs:\n#{stubs}" if !stubs.empty?
212
+ msg += "\nStubs:\n#{stubs}" unless stubs.empty?
151
213
  end
152
214
  raise NotStubbedError, msg, orig_caller.map(&:to_s)
153
215
  end
154
- end
155
- end
156
-
157
- def arity_matches?(args)
158
- return true if @method.arity == args.size # mandatory args
159
- return true if @method.arity < 0 && args.size >= (@method.arity+1).abs # variable args
160
- return false
216
+ end ||
217
+ raise(
218
+ StubError,
219
+ "#{inspect_call(args)} stubbed with no block.",
220
+ orig_caller.map(&:to_s),
221
+ )
161
222
  end
162
223
 
163
224
  def inspect_lookup_stubs
@@ -167,23 +228,38 @@ module MuchStub
167
228
  def inspect_call(args)
168
229
  "`#{@method_name}(#{args.map(&:inspect).join(",")})`"
169
230
  end
170
-
171
- def number_of_args(arity)
172
- if arity < 0
173
- "at least #{(arity + 1).abs}"
174
- else
175
- arity
176
- end
177
- end
178
231
  end
179
232
 
180
233
  StubError = Class.new(ArgumentError)
181
234
  NotStubbedError = Class.new(StubError)
182
- StubArityError = Class.new(StubError)
235
+ StubArityError =
236
+ Class.new(StubError) do
237
+ def initialize(method, args, method_name:, backtrace:)
238
+ msg = "arity mismatch on `#{method_name}`: " \
239
+ "expected #{number_of_args(method.arity)}, " \
240
+ "called with #{args.size}"
183
241
 
184
- NullStub = Class.new do
185
- def teardown; end # no-op
186
- end
242
+ super(msg)
243
+ set_backtrace(Array(backtrace).map(&:to_s))
244
+ end
245
+
246
+ private
247
+
248
+ def number_of_args(arity)
249
+ if arity < 0
250
+ "at least #{(arity + 1).abs}"
251
+ else
252
+ arity
253
+ end
254
+ end
255
+ end
256
+
257
+ NullStub =
258
+ Class.new do
259
+ def teardown
260
+ # no-op
261
+ end
262
+ end
187
263
 
188
264
  module ParameterList
189
265
  LETTERS = ("a".."z").to_a.freeze
@@ -196,8 +272,6 @@ module MuchStub
196
272
  params.join(", ")
197
273
  end
198
274
 
199
- private
200
-
201
275
  def self.get_arity(object, method_name)
202
276
  object.method(method_name).arity
203
277
  rescue NameError